mirror of
https://github.com/wolfpld/tracy.git
synced 2024-11-29 08:54:34 +00:00
Draw CPU zones using a precalculated list.
This commit is contained in:
parent
85d541124e
commit
66d3c2a472
@ -258,7 +258,7 @@ void TimelineItemThread::HeaderExtraContents( const TimelineContext& ctx, int of
|
||||
|
||||
bool TimelineItemThread::DrawContents( const TimelineContext& ctx, int& offset )
|
||||
{
|
||||
const auto res = m_view.DrawThread( ctx, *m_thread, offset, m_ghost );
|
||||
const auto res = m_view.DrawThread( ctx, *m_thread, m_draw, offset, m_depth );
|
||||
if( !res )
|
||||
{
|
||||
auto& crash = m_worker.GetCrashEvent();
|
||||
|
@ -41,6 +41,7 @@ struct MemoryPage;
|
||||
class FileRead;
|
||||
class SourceView;
|
||||
struct TimelineContext;
|
||||
struct TimelineDraw;
|
||||
|
||||
class View
|
||||
{
|
||||
@ -121,7 +122,7 @@ public:
|
||||
void HighlightThread( uint64_t thread );
|
||||
void ZoomToRange( int64_t start, int64_t end, bool pause = true );
|
||||
bool DrawPlot( const TimelineContext& ctx, PlotData& plot, int& offset );
|
||||
bool DrawThread( const TimelineContext& ctx, const ThreadData& thread, int& offset, bool ghostMode );
|
||||
bool DrawThread( const TimelineContext& ctx, const ThreadData& thread, const std::vector<TimelineDraw>& draw, int& offset, int depth );
|
||||
void DrawThreadMessages( const TimelineContext& ctx, const ThreadData& thread, int offset );
|
||||
void DrawThreadOverlays( const ThreadData& thread, const ImVec2& ul, const ImVec2& dr );
|
||||
bool DrawGpu( const TimelineContext& ctx, const GpuCtxData& gpu, int& offset );
|
||||
@ -206,16 +207,7 @@ private:
|
||||
void DrawTimeline();
|
||||
void DrawContextSwitches( const ContextSwitch* ctx, const Vector<SampleData>& sampleData, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int offset, int endOffset, bool isFiber );
|
||||
void DrawSamples( const Vector<SampleData>& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int offset );
|
||||
#ifndef TRACY_NO_STATISTICS
|
||||
int DispatchGhostLevel( const Vector<GhostZone>& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int offset, int depth, float yMin, float yMax, uint64_t tid );
|
||||
int DrawGhostLevel( const Vector<GhostZone>& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int offset, int depth, float yMin, float yMax, uint64_t tid );
|
||||
int SkipGhostLevel( const Vector<GhostZone>& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int offset, int depth, float yMin, float yMax, uint64_t tid );
|
||||
#endif
|
||||
int DispatchZoneLevel( const Vector<short_ptr<ZoneEvent>>& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int offset, int depth, float yMin, float yMax, uint64_t tid );
|
||||
template<typename Adapter, typename V>
|
||||
int DrawZoneLevel( const V& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int offset, int depth, float yMin, float yMax, uint64_t tid );
|
||||
template<typename Adapter, typename V>
|
||||
int SkipZoneLevel( const V& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int offset, int depth, float yMin, float yMax, uint64_t tid );
|
||||
void DrawZoneList( const TimelineContext& ctx, const std::vector<TimelineDraw>& drawList, int offset, uint64_t tid );
|
||||
int DispatchGpuZoneLevel( const Vector<short_ptr<GpuEvent>>& 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<typename Adapter, typename V>
|
||||
int DrawGpuZoneLevel( const V& 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 );
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "TracyMouse.hpp"
|
||||
#include "TracyPrint.hpp"
|
||||
#include "TracyTimelineContext.hpp"
|
||||
#include "TracyTimelineDraw.hpp"
|
||||
#include "TracyView.hpp"
|
||||
|
||||
namespace tracy
|
||||
@ -22,7 +23,7 @@ static tracy_force_inline uint32_t MixGhostColor( uint32_t c0, uint32_t c1 )
|
||||
( ( ( ( ( c0 & 0x000000FF ) ) + 3 * ( ( c1 & 0x000000FF ) ) ) >> 2 ) );
|
||||
}
|
||||
|
||||
bool View::DrawThread( const TimelineContext& ctx, const ThreadData& thread, int& offset, bool ghostMode )
|
||||
bool View::DrawThread( const TimelineContext& ctx, const ThreadData& thread, const std::vector<TimelineDraw>& draw, int& offset, int depth )
|
||||
{
|
||||
const auto& wpos = ctx.wpos;
|
||||
const auto ty = ctx.ty;
|
||||
@ -38,7 +39,6 @@ bool View::DrawThread( const TimelineContext& ctx, const ThreadData& thread, int
|
||||
const auto sstep = sty + 1;
|
||||
ImGui::PopFont();
|
||||
|
||||
int depth = 0;
|
||||
const auto sampleOffset = offset;
|
||||
const auto hasSamples = m_vd.drawSamples && !thread.samples.empty();
|
||||
const auto hasCtxSwitch = m_vd.drawContextSwitches && m_worker.GetContextSwitchData( thread.id );
|
||||
@ -61,15 +61,10 @@ bool View::DrawThread( const TimelineContext& ctx, const ThreadData& thread, int
|
||||
offset += round( ostep * 0.75f );
|
||||
}
|
||||
|
||||
#ifndef TRACY_NO_STATISTICS
|
||||
if( m_worker.AreGhostZonesReady() && ( ghostMode || ( m_vd.ghostZones && thread.timeline.empty() ) ) )
|
||||
const auto yPos = wpos.y + offset;
|
||||
if( !draw.empty() && yPos <= yMax && yPos + ostep * depth >= yMin )
|
||||
{
|
||||
depth = DispatchGhostLevel( thread.ghostZones, hover, pxns, int64_t( nspx ), wpos, offset, 0, yMin, yMax, thread.id );
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
depth = DispatchZoneLevel( thread.timeline, hover, pxns, int64_t( nspx ), wpos, offset, 0, yMin, yMax, thread.id );
|
||||
DrawZoneList( ctx, draw, offset, thread.id );
|
||||
}
|
||||
offset += ostep * depth;
|
||||
|
||||
@ -236,71 +231,226 @@ void View::DrawThreadOverlays( const ThreadData& thread, const ImVec2& ul, const
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef TRACY_NO_STATISTICS
|
||||
int View::DispatchGhostLevel( const Vector<GhostZone>& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int _offset, int depth, float yMin, float yMax, uint64_t tid )
|
||||
void View::DrawZoneList( const TimelineContext& ctx, const std::vector<TimelineDraw>& drawList, int _offset, uint64_t tid )
|
||||
{
|
||||
const auto ty = ImGui::GetTextLineHeight();
|
||||
auto draw = ImGui::GetWindowDrawList();
|
||||
const auto w = ctx.w;
|
||||
const auto& wpos = ctx.wpos;
|
||||
const auto dpos = wpos + ImVec2( 0.5f, 0.5f );
|
||||
const auto ty = ctx.ty;
|
||||
const auto ostep = ty + 1;
|
||||
const auto offset = _offset + ostep * depth;
|
||||
const auto yMin = ctx.yMin;
|
||||
const auto yMax = ctx.yMax;
|
||||
const auto pxns = ctx.pxns;
|
||||
const auto hover = ctx.hover;
|
||||
const auto vStart = ctx.vStart;
|
||||
const auto dsz = m_worker.GetDelay() * pxns;
|
||||
const auto rsz = m_worker.GetResolution() * pxns;
|
||||
|
||||
const auto yPos = wpos.y + offset;
|
||||
// Inline frames have to be taken into account, hence the multiply by 64 (arbitrary limit for inline frames in client)
|
||||
if( yPos + 64 * ostep >= yMin && yPos <= yMax )
|
||||
const auto ty025 = round( ty * 0.25f );
|
||||
const auto ty05 = round( ty * 0.5f );
|
||||
const auto ty075 = round( ty * 0.75f );
|
||||
|
||||
for( auto& v : drawList )
|
||||
{
|
||||
return DrawGhostLevel( vec, hover, pxns, nspx, wpos, _offset, depth, yMin, yMax, tid );
|
||||
const auto offset = _offset + ostep * v.depth;
|
||||
const auto yPos = wpos.y + offset;
|
||||
if( yPos > yMax || yPos + ostep < yMin ) continue;
|
||||
|
||||
switch( v.type )
|
||||
{
|
||||
case TimelineDrawType::Folded:
|
||||
{
|
||||
auto& ev = *(const ZoneEvent*)v.ev.get();
|
||||
const auto color = m_vd.dynamicColors == 2 ? 0xFF666666 : GetThreadColor( tid, v.depth );
|
||||
const auto rend = v.rend.Val();
|
||||
const auto px0 = ( ev.Start() - vStart ) * pxns;
|
||||
const auto px1 = ( rend - vStart ) * pxns;
|
||||
draw->AddRectFilled( wpos + ImVec2( std::max( px0, -10.0 ), offset ), wpos + ImVec2( std::min( std::max( px1, px0+MinVisSize ), double( w + 10 ) ), offset + ty ), color );
|
||||
DrawZigZag( draw, wpos + ImVec2( 0, offset + ty/2 ), std::max( px0, -10.0 ), std::min( std::max( px1, px0+MinVisSize ), double( w + 10 ) ), ty/4, DarkenColor( color ) );
|
||||
if( hover && ImGui::IsMouseHoveringRect( wpos + ImVec2( std::max( px0, -10.0 ), offset ), wpos + ImVec2( std::min( std::max( px1, px0+MinVisSize ), double( w + 10 ) ), offset + ty + 1 ) ) )
|
||||
{
|
||||
if( IsMouseClickReleased( 1 ) ) m_setRangePopup = RangeSlim { ev.Start(), rend, true };
|
||||
if( v.num > 1 )
|
||||
{
|
||||
ImGui::BeginTooltip();
|
||||
TextFocused( "Zones too small to display:", RealToString( v.num ) );
|
||||
ImGui::Separator();
|
||||
TextFocused( "Execution time:", TimeToString( rend - ev.Start() ) );
|
||||
ImGui::EndTooltip();
|
||||
|
||||
if( IsMouseClicked( 2 ) && rend - ev.Start() > 0 )
|
||||
{
|
||||
ZoomToRange( ev.Start(), rend );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return SkipGhostLevel( vec, hover, pxns, nspx, wpos, _offset, depth, yMin, yMax, tid );
|
||||
ZoneTooltip( ev );
|
||||
|
||||
if( IsMouseClicked( 2 ) && rend - ev.Start() > 0 )
|
||||
{
|
||||
ZoomToZone( ev );
|
||||
}
|
||||
if( IsMouseClicked( 0 ) )
|
||||
{
|
||||
if( ImGui::GetIO().KeyCtrl )
|
||||
{
|
||||
auto& srcloc = m_worker.GetSourceLocation( ev.SrcLoc() );
|
||||
m_findZone.ShowZone( ev.SrcLoc(), m_worker.GetString( srcloc.name.active ? srcloc.name : srcloc.function ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowZoneInfo( ev );
|
||||
}
|
||||
}
|
||||
|
||||
int View::DrawGhostLevel( const Vector<GhostZone>& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int _offset, int depth, float yMin, float yMax, uint64_t tid )
|
||||
m_zoneSrcLocHighlight = ev.SrcLoc();
|
||||
m_zoneHover = &ev;
|
||||
}
|
||||
}
|
||||
const auto tmp = RealToString( v.num );
|
||||
const auto tsz = ImGui::CalcTextSize( tmp );
|
||||
if( tsz.x < px1 - px0 )
|
||||
{
|
||||
auto it = std::lower_bound( vec.begin(), vec.end(), std::max<int64_t>( 0, m_vd.zvStart - 2 * MinVisSize * nspx ), [] ( 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(), m_vd.zvEnd, [] ( const auto& l, const auto& r ) { return l.start.Val() < r; } );
|
||||
if( it == zitend ) return depth;
|
||||
if( (zitend-1)->end.Val() < m_vd.zvStart ) return depth;
|
||||
|
||||
const auto w = ImGui::GetContentRegionAvail().x - 1;
|
||||
const auto ty = ImGui::GetTextLineHeight();
|
||||
const auto ostep = ty + 1;
|
||||
const auto offset = _offset + ostep * depth;
|
||||
auto draw = ImGui::GetWindowDrawList();
|
||||
const auto dpos = wpos + ImVec2( 0.5f, 0.5f );
|
||||
|
||||
depth++;
|
||||
int maxdepth = depth;
|
||||
|
||||
while( it < zitend )
|
||||
const auto x = px0 + ( px1 - px0 - tsz.x ) / 2;
|
||||
DrawTextContrast( draw, wpos + ImVec2( x, offset ), 0xFF4488DD, tmp );
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TimelineDrawType::Zone:
|
||||
{
|
||||
auto& ev = *it;
|
||||
const auto end = ev.end.Val();
|
||||
const auto zsz = std::max( ( end - ev.start.Val() ) * pxns, pxns * 0.5 );
|
||||
if( zsz < MinVisSize )
|
||||
auto& ev = *(const ZoneEvent*)v.ev.get();
|
||||
const auto end = m_worker.GetZoneEnd( ev );
|
||||
const auto zsz = std::max( ( end - ev.Start() ) * pxns, pxns * 0.5 );
|
||||
const auto zoneColor = GetZoneColorData( ev, tid, v.depth );
|
||||
const char* zoneName = m_worker.GetZoneName( ev );
|
||||
|
||||
auto tsz = ImGui::CalcTextSize( zoneName );
|
||||
if( m_shortenName == ShortenName::Always || ( ( m_shortenName == ShortenName::NoSpace || m_shortenName == ShortenName::NoSpaceAndNormalize ) && tsz.x > zsz ) )
|
||||
{
|
||||
const auto MinVisNs = MinVisSize * nspx;
|
||||
const auto color = m_vd.dynamicColors == 2 ? 0xFF666666 : MixGhostColor( GetThreadColor( tid, depth ), 0x665555 );
|
||||
zoneName = ShortenZoneName( m_shortenName, zoneName, tsz, zsz );
|
||||
}
|
||||
|
||||
const auto pr0 = ( ev.Start() - m_vd.zvStart ) * pxns;
|
||||
const auto pr1 = ( end - m_vd.zvStart ) * pxns;
|
||||
const auto px0 = std::max( pr0, -10.0 );
|
||||
const auto px1 = std::max( { std::min( pr1, double( w + 10 ) ), px0 + pxns * 0.5, px0 + MinVisSize } );
|
||||
draw->AddRectFilled( wpos + ImVec2( px0, offset ), wpos + ImVec2( px1, offset + tsz.y ), zoneColor.color );
|
||||
if( zoneColor.highlight )
|
||||
{
|
||||
if( zoneColor.thickness > 1.f )
|
||||
{
|
||||
draw->AddRect( wpos + ImVec2( px0 + 1, offset + 1 ), wpos + ImVec2( px1 - 1, offset + tsz.y - 1 ), zoneColor.accentColor, 0.f, -1, zoneColor.thickness );
|
||||
}
|
||||
else
|
||||
{
|
||||
draw->AddRect( wpos + ImVec2( px0, offset ), wpos + ImVec2( px1, offset + tsz.y ), zoneColor.accentColor, 0.f, -1, zoneColor.thickness );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto darkColor = DarkenColor( zoneColor.color );
|
||||
DrawLine( draw, dpos + ImVec2( px0, offset + tsz.y ), dpos + ImVec2( px0, offset ), dpos + ImVec2( px1-1, offset ), zoneColor.accentColor, zoneColor.thickness );
|
||||
DrawLine( draw, dpos + ImVec2( px0, offset + tsz.y ), dpos + ImVec2( px1-1, offset + tsz.y ), dpos + ImVec2( px1-1, offset ), darkColor, zoneColor.thickness );
|
||||
}
|
||||
if( dsz > MinVisSize )
|
||||
{
|
||||
const auto diff = dsz - MinVisSize;
|
||||
uint32_t color;
|
||||
if( diff < 1 )
|
||||
{
|
||||
color = ( uint32_t( diff * 0x88 ) << 24 ) | 0x2222DD;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = 0x882222DD;
|
||||
}
|
||||
|
||||
draw->AddRectFilled( wpos + ImVec2( pr0, offset ), wpos + ImVec2( std::min( pr0+dsz, pr1 ), offset + tsz.y ), color );
|
||||
draw->AddRectFilled( wpos + ImVec2( pr1, offset ), wpos + ImVec2( pr1+dsz, offset + tsz.y ), color );
|
||||
}
|
||||
if( rsz > MinVisSize )
|
||||
{
|
||||
const auto diff = rsz - MinVisSize;
|
||||
uint32_t color;
|
||||
if( diff < 1 )
|
||||
{
|
||||
color = ( uint32_t( diff * 0xAA ) << 24 ) | 0xFFFFFF;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = 0xAAFFFFFF;
|
||||
}
|
||||
|
||||
DrawLine( draw, dpos + ImVec2( pr0 + rsz, offset + ty05 ), dpos + ImVec2( pr0 - rsz, offset + ty05 ), color );
|
||||
DrawLine( draw, dpos + ImVec2( pr0 + rsz, offset + ty025 ), dpos + ImVec2( pr0 + rsz, offset + ty075 ), color );
|
||||
DrawLine( draw, dpos + ImVec2( pr0 - rsz, offset + ty025 ), dpos + ImVec2( pr0 - rsz, offset + ty075 ), color );
|
||||
|
||||
DrawLine( draw, dpos + ImVec2( pr1 + rsz, offset + ty05 ), dpos + ImVec2( pr1 - rsz, offset + ty05 ), color );
|
||||
DrawLine( draw, dpos + ImVec2( pr1 + rsz, offset + ty025 ), dpos + ImVec2( pr1 + rsz, offset + ty075 ), color );
|
||||
DrawLine( draw, dpos + ImVec2( pr1 - rsz, offset + ty025 ), dpos + ImVec2( pr1 - rsz, offset + ty075 ), color );
|
||||
}
|
||||
if( tsz.x < zsz )
|
||||
{
|
||||
const auto x = ( ev.Start() - m_vd.zvStart ) * pxns + ( ( end - ev.Start() ) * pxns - tsz.x ) / 2;
|
||||
if( x < 0 || x > w - tsz.x )
|
||||
{
|
||||
ImGui::PushClipRect( wpos + ImVec2( px0, offset ), wpos + ImVec2( px1, offset + tsz.y * 2 ), true );
|
||||
DrawTextContrast( draw, wpos + ImVec2( std::max( std::max( 0., px0 ), std::min( double( w - tsz.x ), x ) ), offset ), 0xFFFFFFFF, zoneName );
|
||||
ImGui::PopClipRect();
|
||||
}
|
||||
else if( ev.Start() == ev.End() )
|
||||
{
|
||||
DrawTextContrast( draw, wpos + ImVec2( px0 + ( px1 - px0 - tsz.x ) * 0.5, offset ), 0xFFFFFFFF, zoneName );
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawTextContrast( draw, wpos + ImVec2( x, offset ), 0xFFFFFFFF, zoneName );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::PushClipRect( wpos + ImVec2( px0, offset ), wpos + ImVec2( px1, offset + tsz.y * 2 ), true );
|
||||
DrawTextContrast( draw, wpos + ImVec2( std::max( int64_t( 0 ), ev.Start() - m_vd.zvStart ) * pxns, offset ), 0xFFFFFFFF, zoneName );
|
||||
ImGui::PopClipRect();
|
||||
}
|
||||
|
||||
if( hover && ImGui::IsMouseHoveringRect( wpos + ImVec2( px0, offset ), wpos + ImVec2( px1, offset + tsz.y + 1 ) ) )
|
||||
{
|
||||
ZoneTooltip( ev );
|
||||
if( IsMouseClickReleased( 1 ) ) m_setRangePopup = RangeSlim { ev.Start(), m_worker.GetZoneEnd( ev ), true };
|
||||
|
||||
if( !m_zoomAnim.active && IsMouseClicked( 2 ) )
|
||||
{
|
||||
ZoomToZone( ev );
|
||||
}
|
||||
if( IsMouseClicked( 0 ) )
|
||||
{
|
||||
if( ImGui::GetIO().KeyCtrl )
|
||||
{
|
||||
auto& srcloc = m_worker.GetSourceLocation( ev.SrcLoc() );
|
||||
m_findZone.ShowZone( ev.SrcLoc(), m_worker.GetString( srcloc.name.active ? srcloc.name : srcloc.function ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowZoneInfo( ev );
|
||||
}
|
||||
}
|
||||
|
||||
m_zoneSrcLocHighlight = ev.SrcLoc();
|
||||
m_zoneHover = &ev;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#ifndef TRACY_NO_STATISTICS
|
||||
case TimelineDrawType::GhostFolded:
|
||||
{
|
||||
auto& ev = *(const GhostZone*)v.ev.get();
|
||||
const auto color = m_vd.dynamicColors == 2 ? 0xFF666666 : MixGhostColor( GetThreadColor( tid, v.depth ), 0x665555 );
|
||||
const auto rend = v.rend.Val();
|
||||
const auto px0 = ( ev.start.Val() - m_vd.zvStart ) * pxns;
|
||||
auto px1ns = ev.end.Val() - m_vd.zvStart;
|
||||
auto rend = end;
|
||||
auto nextTime = end + MinVisNs;
|
||||
for(;;)
|
||||
{
|
||||
const auto prevIt = it;
|
||||
it = std::lower_bound( it, zitend, nextTime, [] ( const auto& l, const auto& r ) { return l.end.Val() < r; } );
|
||||
if( it == prevIt ) ++it;
|
||||
if( it == zitend ) break;
|
||||
const auto nend = it->end.Val();
|
||||
const auto nsnext = nend - m_vd.zvStart;
|
||||
if( nsnext - px1ns >= MinVisNs * 2 ) break;
|
||||
px1ns = nsnext;
|
||||
rend = nend;
|
||||
nextTime = nend + nspx;
|
||||
}
|
||||
const auto px1 = px1ns * pxns;
|
||||
const auto px1 = ( rend - ev.end.Val() ) * pxns;
|
||||
draw->AddRectFilled( wpos + ImVec2( std::max( px0, -10.0 ), offset ), wpos + ImVec2( std::min( std::max( px1, px0+MinVisSize ), double( w + 10 ) ), offset + ty ), color );
|
||||
DrawZigZag( draw, wpos + ImVec2( 0, offset + ty/2 ), std::max( px0, -10.0 ), std::min( std::max( px1, px0+MinVisSize ), double( w + 10 ) ), ty/4, DarkenColor( color ) );
|
||||
if( hover && ImGui::IsMouseHoveringRect( wpos + ImVec2( std::max( px0, -10.0 ), offset ), wpos + ImVec2( std::min( std::max( px1, px0+MinVisSize ), double( w + 10 ) ), offset + ty + 1 ) ) )
|
||||
@ -317,9 +467,14 @@ int View::DrawGhostLevel( const Vector<GhostZone>& vec, bool hover, double pxns,
|
||||
ZoomToRange( ev.start.Val(), rend );
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
case TimelineDrawType::Ghost:
|
||||
{
|
||||
auto& ev = *(const GhostZone*)v.ev.get();
|
||||
const auto end = ev.end.Val();
|
||||
const auto zsz = std::max( ( end - ev.start.Val() ) * pxns, pxns * 0.5 );
|
||||
|
||||
const auto& ghostKey = m_worker.GetGhostFrame( ev.frame );
|
||||
const auto frame = m_worker.GetCallstackFrame( ghostKey.frame );
|
||||
|
||||
@ -329,16 +484,16 @@ int View::DrawGhostLevel( const Vector<GhostZone>& vec, bool hover, double pxns,
|
||||
if( frame )
|
||||
{
|
||||
const auto& sym = frame->data[ghostKey.inlineFrame];
|
||||
color = GetHsvColor( sym.name.Idx(), depth );
|
||||
color = GetHsvColor( sym.name.Idx(), v.depth );
|
||||
}
|
||||
else
|
||||
{
|
||||
color = GetHsvColor( ghostKey.frame.data, depth );
|
||||
color = GetHsvColor( ghostKey.frame.data, v.depth );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
color = MixGhostColor( GetThreadColor( tid, depth ), 0x665555 );
|
||||
color = MixGhostColor( GetThreadColor( tid, v.depth ), 0x665555 );
|
||||
}
|
||||
|
||||
const auto pr0 = ( ev.start.Val() - m_vd.zvStart ) * pxns;
|
||||
@ -520,396 +675,14 @@ int View::DrawGhostLevel( const Vector<GhostZone>& vec, bool hover, double pxns,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( ev.child >= 0 )
|
||||
{
|
||||
const auto d = DispatchGhostLevel( m_worker.GetGhostChildren( ev.child ), hover, pxns, nspx, wpos, _offset, depth, yMin, yMax, tid );
|
||||
if( d > maxdepth ) maxdepth = d;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
return maxdepth;
|
||||
}
|
||||
|
||||
int View::SkipGhostLevel( const Vector<GhostZone>& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int _offset, int depth, float yMin, float yMax, uint64_t tid )
|
||||
{
|
||||
auto it = std::lower_bound( vec.begin(), vec.end(), std::max<int64_t>( 0, m_vd.zvStart - 2 * MinVisSize * nspx ), [] ( 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(), m_vd.zvEnd, [] ( const auto& l, const auto& r ) { return l.start.Val() < r; } );
|
||||
if( it == zitend ) return depth;
|
||||
if( (zitend-1)->end.Val() < m_vd.zvStart ) return depth;
|
||||
|
||||
depth++;
|
||||
int maxdepth = depth;
|
||||
|
||||
while( it < zitend )
|
||||
{
|
||||
auto& ev = *it;
|
||||
const auto end = ev.end.Val();
|
||||
const auto zsz = std::max( ( end - ev.start.Val() ) * pxns, pxns * 0.5 );
|
||||
if( zsz < MinVisSize )
|
||||
{
|
||||
const auto MinVisNs = MinVisSize * nspx;
|
||||
auto px1ns = ev.end.Val() - m_vd.zvStart;
|
||||
auto nextTime = end + MinVisNs;
|
||||
for(;;)
|
||||
{
|
||||
const auto prevIt = it;
|
||||
it = std::lower_bound( it, zitend, nextTime, [] ( const auto& l, const auto& r ) { return l.end.Val() < r; } );
|
||||
if( it == prevIt ) ++it;
|
||||
if( it == zitend ) break;
|
||||
const auto nend = it->end.Val();
|
||||
const auto nsnext = nend - m_vd.zvStart;
|
||||
if( nsnext - px1ns >= MinVisNs * 2 ) break;
|
||||
px1ns = nsnext;
|
||||
nextTime = nend + nspx;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( ev.child >= 0 )
|
||||
{
|
||||
const auto d = DispatchGhostLevel( m_worker.GetGhostChildren( ev.child ), hover, pxns, nspx, wpos, _offset, depth, yMin, yMax, tid );
|
||||
if( d > maxdepth ) maxdepth = d;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
return maxdepth;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
int View::DispatchZoneLevel( const Vector<short_ptr<ZoneEvent>>& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int _offset, int depth, float yMin, float yMax, uint64_t tid )
|
||||
{
|
||||
const auto ty = ImGui::GetTextLineHeight();
|
||||
const auto ostep = ty + 1;
|
||||
const auto offset = _offset + ostep * depth;
|
||||
|
||||
const auto yPos = wpos.y + offset;
|
||||
if( yPos + ostep >= yMin && yPos <= yMax )
|
||||
{
|
||||
if( vec.is_magic() )
|
||||
{
|
||||
return DrawZoneLevel<VectorAdapterDirect<ZoneEvent>>( *(Vector<ZoneEvent>*)( &vec ), hover, pxns, nspx, wpos, _offset, depth, yMin, yMax, tid );
|
||||
}
|
||||
else
|
||||
{
|
||||
return DrawZoneLevel<VectorAdapterPointer<ZoneEvent>>( vec, hover, pxns, nspx, wpos, _offset, depth, yMin, yMax, tid );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( vec.is_magic() )
|
||||
{
|
||||
return SkipZoneLevel<VectorAdapterDirect<ZoneEvent>>( *(Vector<ZoneEvent>*)( &vec ), hover, pxns, nspx, wpos, _offset, depth, yMin, yMax, tid );
|
||||
}
|
||||
else
|
||||
{
|
||||
return SkipZoneLevel<VectorAdapterPointer<ZoneEvent>>( vec, hover, pxns, nspx, wpos, _offset, depth, yMin, yMax, tid );
|
||||
default:
|
||||
assert( false );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Adapter, typename V>
|
||||
int View::DrawZoneLevel( const V& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int _offset, int depth, float yMin, float yMax, uint64_t tid )
|
||||
{
|
||||
const auto delay = m_worker.GetDelay();
|
||||
const auto resolution = m_worker.GetResolution();
|
||||
// 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, m_vd.zvStart - std::max<int64_t>( delay, 2 * MinVisSize * nspx ) ), [] ( 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(), m_vd.zvEnd + 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) ) < m_vd.zvStart ) return depth;
|
||||
if( m_worker.GetZoneEnd( a(*(zitend-1)) ) < m_vd.zvStart ) return depth;
|
||||
|
||||
const auto w = ImGui::GetContentRegionAvail().x - 1;
|
||||
const auto ty = ImGui::GetTextLineHeight();
|
||||
const auto ostep = ty + 1;
|
||||
const auto offset = _offset + ostep * depth;
|
||||
auto draw = ImGui::GetWindowDrawList();
|
||||
const auto dsz = delay * pxns;
|
||||
const auto rsz = resolution * pxns;
|
||||
const auto dpos = wpos + ImVec2( 0.5f, 0.5f );
|
||||
|
||||
const auto ty025 = round( ty * 0.25f );
|
||||
const auto ty05 = round( ty * 0.5f );
|
||||
const auto ty075 = round( ty * 0.75f );
|
||||
|
||||
depth++;
|
||||
int maxdepth = depth;
|
||||
|
||||
while( it < zitend )
|
||||
{
|
||||
auto& ev = a(*it);
|
||||
const auto end = m_worker.GetZoneEnd( ev );
|
||||
const auto zsz = std::max( ( end - ev.Start() ) * pxns, pxns * 0.5 );
|
||||
if( zsz < MinVisSize )
|
||||
{
|
||||
const auto MinVisNs = MinVisSize * nspx;
|
||||
const auto color = m_vd.dynamicColors == 2 ? 0xFF666666 : GetThreadColor( tid, depth );
|
||||
int num = 0;
|
||||
const auto px0 = ( ev.Start() - m_vd.zvStart ) * pxns;
|
||||
auto px1ns = end - m_vd.zvStart;
|
||||
auto rend = end;
|
||||
auto nextTime = end + MinVisNs;
|
||||
for(;;)
|
||||
{
|
||||
const auto prevIt = it;
|
||||
it = std::lower_bound( it, zitend, nextTime, [] ( const auto& l, const auto& r ) { Adapter a; return (uint64_t)a(l).End() < (uint64_t)r; } );
|
||||
if( it == prevIt ) ++it;
|
||||
num += std::distance( prevIt, it );
|
||||
if( it == zitend ) break;
|
||||
const auto nend = m_worker.GetZoneEnd( a(*it) );
|
||||
const auto nsnext = nend - m_vd.zvStart;
|
||||
if( nsnext - px1ns >= MinVisNs * 2 ) break;
|
||||
px1ns = nsnext;
|
||||
rend = nend;
|
||||
nextTime = nend + nspx;
|
||||
}
|
||||
const auto px1 = px1ns * pxns;
|
||||
draw->AddRectFilled( wpos + ImVec2( std::max( px0, -10.0 ), offset ), wpos + ImVec2( std::min( std::max( px1, px0+MinVisSize ), double( w + 10 ) ), offset + ty ), color );
|
||||
DrawZigZag( draw, wpos + ImVec2( 0, offset + ty/2 ), std::max( px0, -10.0 ), std::min( std::max( px1, px0+MinVisSize ), double( w + 10 ) ), ty/4, DarkenColor( color ) );
|
||||
if( hover && ImGui::IsMouseHoveringRect( wpos + ImVec2( std::max( px0, -10.0 ), offset ), wpos + ImVec2( std::min( std::max( px1, px0+MinVisSize ), double( w + 10 ) ), offset + ty + 1 ) ) )
|
||||
{
|
||||
if( IsMouseClickReleased( 1 ) ) m_setRangePopup = RangeSlim { ev.Start(), rend, true };
|
||||
if( num > 1 )
|
||||
{
|
||||
ImGui::BeginTooltip();
|
||||
TextFocused( "Zones too small to display:", RealToString( num ) );
|
||||
ImGui::Separator();
|
||||
TextFocused( "Execution time:", TimeToString( rend - ev.Start() ) );
|
||||
ImGui::EndTooltip();
|
||||
|
||||
if( IsMouseClicked( 2 ) && rend - ev.Start() > 0 )
|
||||
{
|
||||
ZoomToRange( ev.Start(), rend );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ZoneTooltip( ev );
|
||||
|
||||
if( IsMouseClicked( 2 ) && rend - ev.Start() > 0 )
|
||||
{
|
||||
ZoomToZone( ev );
|
||||
}
|
||||
if( IsMouseClicked( 0 ) )
|
||||
{
|
||||
if( ImGui::GetIO().KeyCtrl )
|
||||
{
|
||||
auto& srcloc = m_worker.GetSourceLocation( ev.SrcLoc() );
|
||||
m_findZone.ShowZone( ev.SrcLoc(), m_worker.GetString( srcloc.name.active ? srcloc.name : srcloc.function ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowZoneInfo( ev );
|
||||
}
|
||||
}
|
||||
|
||||
m_zoneSrcLocHighlight = ev.SrcLoc();
|
||||
m_zoneHover = &ev;
|
||||
}
|
||||
}
|
||||
const auto tmp = RealToString( num );
|
||||
const auto tsz = ImGui::CalcTextSize( tmp );
|
||||
if( tsz.x < px1 - px0 )
|
||||
{
|
||||
const auto x = px0 + ( px1 - px0 - tsz.x ) / 2;
|
||||
DrawTextContrast( draw, wpos + ImVec2( x, offset ), 0xFF4488DD, tmp );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto zoneColor = GetZoneColorData( ev, tid, depth );
|
||||
const char* zoneName = m_worker.GetZoneName( ev );
|
||||
|
||||
if( ev.HasChildren() )
|
||||
{
|
||||
const auto d = DispatchZoneLevel( m_worker.GetZoneChildren( ev.Child() ), hover, pxns, nspx, wpos, _offset, depth, yMin, yMax, tid );
|
||||
if( d > maxdepth ) maxdepth = d;
|
||||
}
|
||||
|
||||
auto tsz = ImGui::CalcTextSize( zoneName );
|
||||
if( m_shortenName == ShortenName::Always || ( ( m_shortenName == ShortenName::NoSpace || m_shortenName == ShortenName::NoSpaceAndNormalize ) && tsz.x > zsz ) )
|
||||
{
|
||||
zoneName = ShortenZoneName( m_shortenName, zoneName, tsz, zsz );
|
||||
}
|
||||
|
||||
const auto pr0 = ( ev.Start() - m_vd.zvStart ) * pxns;
|
||||
const auto pr1 = ( end - m_vd.zvStart ) * pxns;
|
||||
const auto px0 = std::max( pr0, -10.0 );
|
||||
const auto px1 = std::max( { std::min( pr1, double( w + 10 ) ), px0 + pxns * 0.5, px0 + MinVisSize } );
|
||||
draw->AddRectFilled( wpos + ImVec2( px0, offset ), wpos + ImVec2( px1, offset + tsz.y ), zoneColor.color );
|
||||
if( zoneColor.highlight )
|
||||
{
|
||||
if( zoneColor.thickness > 1.f )
|
||||
{
|
||||
draw->AddRect( wpos + ImVec2( px0 + 1, offset + 1 ), wpos + ImVec2( px1 - 1, offset + tsz.y - 1 ), zoneColor.accentColor, 0.f, -1, zoneColor.thickness );
|
||||
}
|
||||
else
|
||||
{
|
||||
draw->AddRect( wpos + ImVec2( px0, offset ), wpos + ImVec2( px1, offset + tsz.y ), zoneColor.accentColor, 0.f, -1, zoneColor.thickness );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto darkColor = DarkenColor( zoneColor.color );
|
||||
DrawLine( draw, dpos + ImVec2( px0, offset + tsz.y ), dpos + ImVec2( px0, offset ), dpos + ImVec2( px1-1, offset ), zoneColor.accentColor, zoneColor.thickness );
|
||||
DrawLine( draw, dpos + ImVec2( px0, offset + tsz.y ), dpos + ImVec2( px1-1, offset + tsz.y ), dpos + ImVec2( px1-1, offset ), darkColor, zoneColor.thickness );
|
||||
}
|
||||
if( dsz > MinVisSize )
|
||||
{
|
||||
const auto diff = dsz - MinVisSize;
|
||||
uint32_t color;
|
||||
if( diff < 1 )
|
||||
{
|
||||
color = ( uint32_t( diff * 0x88 ) << 24 ) | 0x2222DD;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = 0x882222DD;
|
||||
}
|
||||
|
||||
draw->AddRectFilled( wpos + ImVec2( pr0, offset ), wpos + ImVec2( std::min( pr0+dsz, pr1 ), offset + tsz.y ), color );
|
||||
draw->AddRectFilled( wpos + ImVec2( pr1, offset ), wpos + ImVec2( pr1+dsz, offset + tsz.y ), color );
|
||||
}
|
||||
if( rsz > MinVisSize )
|
||||
{
|
||||
const auto diff = rsz - MinVisSize;
|
||||
uint32_t color;
|
||||
if( diff < 1 )
|
||||
{
|
||||
color = ( uint32_t( diff * 0xAA ) << 24 ) | 0xFFFFFF;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = 0xAAFFFFFF;
|
||||
}
|
||||
|
||||
DrawLine( draw, dpos + ImVec2( pr0 + rsz, offset + ty05 ), dpos + ImVec2( pr0 - rsz, offset + ty05 ), color );
|
||||
DrawLine( draw, dpos + ImVec2( pr0 + rsz, offset + ty025 ), dpos + ImVec2( pr0 + rsz, offset + ty075 ), color );
|
||||
DrawLine( draw, dpos + ImVec2( pr0 - rsz, offset + ty025 ), dpos + ImVec2( pr0 - rsz, offset + ty075 ), color );
|
||||
|
||||
DrawLine( draw, dpos + ImVec2( pr1 + rsz, offset + ty05 ), dpos + ImVec2( pr1 - rsz, offset + ty05 ), color );
|
||||
DrawLine( draw, dpos + ImVec2( pr1 + rsz, offset + ty025 ), dpos + ImVec2( pr1 + rsz, offset + ty075 ), color );
|
||||
DrawLine( draw, dpos + ImVec2( pr1 - rsz, offset + ty025 ), dpos + ImVec2( pr1 - rsz, offset + ty075 ), color );
|
||||
}
|
||||
if( tsz.x < zsz )
|
||||
{
|
||||
const auto x = ( ev.Start() - m_vd.zvStart ) * pxns + ( ( end - ev.Start() ) * pxns - tsz.x ) / 2;
|
||||
if( x < 0 || x > w - tsz.x )
|
||||
{
|
||||
ImGui::PushClipRect( wpos + ImVec2( px0, offset ), wpos + ImVec2( px1, offset + tsz.y * 2 ), true );
|
||||
DrawTextContrast( draw, wpos + ImVec2( std::max( std::max( 0., px0 ), std::min( double( w - tsz.x ), x ) ), offset ), 0xFFFFFFFF, zoneName );
|
||||
ImGui::PopClipRect();
|
||||
}
|
||||
else if( ev.Start() == ev.End() )
|
||||
{
|
||||
DrawTextContrast( draw, wpos + ImVec2( px0 + ( px1 - px0 - tsz.x ) * 0.5, offset ), 0xFFFFFFFF, zoneName );
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawTextContrast( draw, wpos + ImVec2( x, offset ), 0xFFFFFFFF, zoneName );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::PushClipRect( wpos + ImVec2( px0, offset ), wpos + ImVec2( px1, offset + tsz.y * 2 ), true );
|
||||
DrawTextContrast( draw, wpos + ImVec2( std::max( int64_t( 0 ), ev.Start() - m_vd.zvStart ) * pxns, offset ), 0xFFFFFFFF, zoneName );
|
||||
ImGui::PopClipRect();
|
||||
}
|
||||
|
||||
if( hover && ImGui::IsMouseHoveringRect( wpos + ImVec2( px0, offset ), wpos + ImVec2( px1, offset + tsz.y + 1 ) ) )
|
||||
{
|
||||
ZoneTooltip( ev );
|
||||
if( IsMouseClickReleased( 1 ) ) m_setRangePopup = RangeSlim { ev.Start(), m_worker.GetZoneEnd( ev ), true };
|
||||
|
||||
if( !m_zoomAnim.active && IsMouseClicked( 2 ) )
|
||||
{
|
||||
ZoomToZone( ev );
|
||||
}
|
||||
if( IsMouseClicked( 0 ) )
|
||||
{
|
||||
if( ImGui::GetIO().KeyCtrl )
|
||||
{
|
||||
auto& srcloc = m_worker.GetSourceLocation( ev.SrcLoc() );
|
||||
m_findZone.ShowZone( ev.SrcLoc(), m_worker.GetString( srcloc.name.active ? srcloc.name : srcloc.function ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowZoneInfo( ev );
|
||||
}
|
||||
}
|
||||
|
||||
m_zoneSrcLocHighlight = ev.SrcLoc();
|
||||
m_zoneHover = &ev;
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
}
|
||||
return maxdepth;
|
||||
}
|
||||
|
||||
template<typename Adapter, typename V>
|
||||
int View::SkipZoneLevel( const V& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int _offset, int depth, float yMin, float yMax, uint64_t tid )
|
||||
{
|
||||
const auto delay = m_worker.GetDelay();
|
||||
const auto resolution = m_worker.GetResolution();
|
||||
// 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, m_vd.zvStart - std::max<int64_t>( delay, 2 * MinVisSize * nspx ) ), [] ( const auto& l, const auto& r ) { Adapter a; return (uint64_t)a(l).End() < (uint64_t)r; } );
|
||||
if( it == vec.end() ) return depth;
|
||||
|
||||
Adapter a;
|
||||
|
||||
const auto zitend = std::lower_bound( it, vec.end(), m_vd.zvEnd + resolution, [] ( const auto& l, const auto& r ) { Adapter a; return a(l).Start() < r; } );
|
||||
if( it == zitend ) return depth;
|
||||
if( m_worker.GetZoneEnd( a(*(zitend-1)) ) < m_vd.zvStart ) return depth;
|
||||
|
||||
depth++;
|
||||
int maxdepth = depth;
|
||||
|
||||
while( it < zitend )
|
||||
{
|
||||
auto& ev = a(*it);
|
||||
const auto end = m_worker.GetZoneEnd( ev );
|
||||
const auto zsz = std::max( ( end - ev.Start() ) * pxns, pxns * 0.5 );
|
||||
if( zsz < MinVisSize )
|
||||
{
|
||||
const auto MinVisNs = MinVisSize * nspx;
|
||||
auto px1ns = end - m_vd.zvStart;
|
||||
auto nextTime = end + MinVisNs;
|
||||
for(;;)
|
||||
{
|
||||
const auto prevIt = it;
|
||||
it = std::lower_bound( it, zitend, nextTime, [] ( const auto& l, const auto& r ) { Adapter a; return (uint64_t)a(l).End() < (uint64_t)r; } );
|
||||
if( it == prevIt ) ++it;
|
||||
if( it == zitend ) break;
|
||||
const auto nend = m_worker.GetZoneEnd( a(*it) );
|
||||
const auto nsnext = nend - m_vd.zvStart;
|
||||
if( nsnext - px1ns >= MinVisNs * 2 ) break;
|
||||
px1ns = nsnext;
|
||||
nextTime = nend + nspx;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( ev.HasChildren() )
|
||||
{
|
||||
const auto d = DispatchZoneLevel( m_worker.GetZoneChildren( ev.Child() ), hover, pxns, nspx, wpos, _offset, depth, yMin, yMax, tid );
|
||||
if( d > maxdepth ) maxdepth = d;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
return maxdepth;
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user