748 lines
24 KiB
C++
748 lines
24 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Widgets/SProfilerThreadView.h"
|
|
|
|
#if STATS
|
|
|
|
#include "Brushes/SlateColorBrush.h"
|
|
#include "Styling/AppStyle.h"
|
|
|
|
|
|
SProfilerThreadView::SProfilerThreadView()
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
SProfilerThreadView::~SProfilerThreadView()
|
|
{
|
|
|
|
}
|
|
|
|
void SProfilerThreadView::Construct( const FArguments& InArgs )
|
|
{
|
|
BindCommands();
|
|
}
|
|
|
|
void SProfilerThreadView::Reset()
|
|
{
|
|
ProfilerStream = nullptr;
|
|
|
|
FMemory::Memzero( PaintStateMemory );
|
|
PaintState = nullptr;
|
|
|
|
MousePosition = FVector2D::ZeroVector;
|
|
LastMousePosition = FVector2D::ZeroVector;
|
|
MousePositionOnButtonDown = FVector2D::ZeroVector;
|
|
|
|
PositionXMS = 0.0f;
|
|
PositionY = 0.0f;
|
|
RangeXMS = 0.0f;
|
|
RangeY = 0.0f;
|
|
TotalRangeXMS = 0.0f;
|
|
TotalRangeY = 0.0f;
|
|
ZoomFactorX = 1.0f;
|
|
|
|
NumMillisecondsPerWindow = NUM_MILLISECONDS_PER_WINDOW;
|
|
NumPixelsPerMillisecond = 0.0f;
|
|
NumMillisecondsPerSample = 0.0f;
|
|
|
|
HoveredFrameIndex = 0;
|
|
HoveredThreadID = 0;
|
|
HoveredPositionX = 0.0f;
|
|
HoveredPositionY = 0.0f;
|
|
|
|
DistanceDragged = 0.0f;
|
|
|
|
bIsLeftMousePressed = false;
|
|
bIsRightMousePressed = false;
|
|
bUpdateData = false;
|
|
CursorType = EThreadViewCursor::Default;
|
|
}
|
|
|
|
void SProfilerThreadView::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
|
|
{
|
|
if( AllottedGeometry.Size.X > 0.0f )
|
|
{
|
|
if( ThisGeometry.Size.X != AllottedGeometry.Size.X )
|
|
{
|
|
// Refresh.
|
|
ThisGeometry = AllottedGeometry;
|
|
bUpdateData = true;
|
|
}
|
|
|
|
if( ShouldUpdateData() && IsReady() )
|
|
{
|
|
UpdateInternalConstants();
|
|
ProcessData();
|
|
bUpdateData = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 SProfilerThreadView::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
|
|
{
|
|
// SCOPE_LOG_TIME_FUNC();
|
|
|
|
// Rendering info.
|
|
const bool bEnabled = ShouldBeEnabled( bParentEnabled );
|
|
const ESlateDrawEffect DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
|
const FSlateBrush* BackgroundBrush = FAppStyle::GetBrush( "Brushes.White25" );
|
|
const FSlateBrush* WhiteBrush = FAppStyle::GetBrush( "Brushes.White" );
|
|
|
|
// Paint state for this call to OnPaint, valid only in this scope.
|
|
PaintState = new((void*)PaintStateMemory) FSlateOnPaintState( AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, DrawEffects );
|
|
|
|
// Draw background.
|
|
FSlateDrawElement::MakeBox
|
|
(
|
|
PaintState->OutDrawElements,
|
|
PaintState->LayerId,
|
|
PaintState->AllottedGeometry.ToPaintGeometry( PaintState->Size2D(), FSlateLayoutTransform() ),
|
|
BackgroundBrush,
|
|
PaintState->DrawEffects,
|
|
BackgroundBrush->GetTint( InWidgetStyle ) * InWidgetStyle.GetColorAndOpacityTint()
|
|
);
|
|
LayerId++;
|
|
|
|
// Draw all cycle counters for each thread nodes.
|
|
if( IsReady() )
|
|
{
|
|
DrawFramesBackgroundAndTimelines();
|
|
DrawUIStackNodes();
|
|
DrawFrameMarkers();
|
|
}
|
|
|
|
#if 0/*DEBUG_PROFILER_PERFORMANCE*/
|
|
|
|
LayerId++;
|
|
// Draw debug information.
|
|
float GraphDescPosY = PaintState->Size2D().Y - 4.0f * PaintState->SummaryFont8Height;
|
|
|
|
// Debug text.
|
|
FSlateDrawElement::MakeText
|
|
(
|
|
OutDrawElements,
|
|
LayerId,
|
|
AllottedGeometry.ToOffsetPaintGeometry( FVector2D( 16.0f, GraphDescPosY ) ),
|
|
FString::Printf( TEXT( "Pos X=%f,Y=%f R X=%f,Y=%f TR X=%f,Y=%f ZF X=%f" ), PositionXMS, PositionY, RangeXMS, RangeY, TotalRangeXMS, TotalRangeY, ZoomFactorX ),
|
|
PaintState->SummaryFont8,
|
|
MyCullingRect,
|
|
DrawEffects,
|
|
FLinearColor::White
|
|
);
|
|
GraphDescPosY -= PaintState->SummaryFont8Height + 1.0f;
|
|
|
|
FSlateDrawElement::MakeText
|
|
(
|
|
OutDrawElements,
|
|
LayerId,
|
|
AllottedGeometry.ToOffsetPaintGeometry( FVector2D( 16.0f, GraphDescPosY ) ),
|
|
FString::Printf( TEXT( "NumMSPerWin=%f H Fr=%i,TID=%i,PX=%f,PY=%f" ), NumMillisecondsPerWindow, HoveredFrameIndex, HoveredThreadID, HoveredPositionX, HoveredPositionY ),
|
|
PaintState->SummaryFont8,
|
|
MyCullingRect,
|
|
DrawEffects,
|
|
FLinearColor::White
|
|
);
|
|
GraphDescPosY -= PaintState->SummaryFont8Height + 1.0f;
|
|
|
|
FSlateDrawElement::MakeText
|
|
(
|
|
OutDrawElements,
|
|
LayerId,
|
|
AllottedGeometry.ToOffsetPaintGeometry( FVector2D( 16.0f, GraphDescPosY ) ),
|
|
FString::Printf( TEXT( "DistD=%.2f FI=%3i,%3i" ), DistanceDragged, FramesIndices.X, FramesIndices.Y ),
|
|
PaintState->SummaryFont8,
|
|
MyCullingRect,
|
|
DrawEffects,
|
|
FLinearColor::White
|
|
);
|
|
GraphDescPosY -= PaintState->SummaryFont8Height + 1.0f;
|
|
|
|
#endif // DEBUG_PROFILER_PERFORMANCE
|
|
|
|
// Reset paint state.
|
|
PaintState = nullptr;
|
|
|
|
return SCompoundWidget::OnPaint( Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled && IsEnabled() );
|
|
}
|
|
|
|
|
|
void SProfilerThreadView::DrawFramesBackgroundAndTimelines() const
|
|
{
|
|
static const FSlateColorBrush SolidWhiteBrush = FSlateColorBrush( FColorList::White );
|
|
|
|
check( PaintState );
|
|
const double ThreadViewOffsetPx = PositionXMS*NumPixelsPerMillisecond;
|
|
PaintState->LayerId++;
|
|
TArray<FVector2D> LinePoints;
|
|
|
|
// Draw frames background for easier reading.
|
|
for( const auto& ThreadNode : ProfilerUIStream.ThreadNodes )
|
|
{
|
|
if( ThreadNode.StatName == NAME_GameThread )
|
|
{
|
|
const FVector2D PositionPx = ThreadNode.GetLocalPosition( ThreadViewOffsetPx, -1.0f );
|
|
const FVector2D SizePx = FVector2D( ThreadNode.WidthPx, PaintState->Size2D().Y );
|
|
|
|
const FSlateRect ClippedFrameBackgroundRect = PaintState->LocalClippingRect.IntersectionWith( FSlateRect( PositionPx, PositionPx + SizePx ) );
|
|
|
|
FSlateDrawElement::MakeBox
|
|
(
|
|
PaintState->OutDrawElements,
|
|
PaintState->LayerId,
|
|
PaintState->AllottedGeometry.ToPaintGeometry( ClippedFrameBackgroundRect.GetSize(), FSlateLayoutTransform(ClippedFrameBackgroundRect.GetTopLeft()) ),
|
|
&SolidWhiteBrush,
|
|
PaintState->DrawEffects,
|
|
(ThreadNode.FrameIndex % 2) ? FColorList::White.WithAlpha( 64 ) : FColorList::White.WithAlpha( 128 )
|
|
);
|
|
|
|
// Check if this frame time marker is inside the visible area.
|
|
const float LocalPositionXPx = static_cast<float>(PositionPx.X + SizePx.X);
|
|
if( LocalPositionXPx < 0.0f || LocalPositionXPx > PaintState->Size2D().X )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
LinePoints.Reset( 2 );
|
|
LinePoints.Add( FVector2D( LocalPositionXPx, 0.0f ) );
|
|
LinePoints.Add( FVector2D( LocalPositionXPx, PaintState->Size2D().Y ) );
|
|
|
|
// Draw frame time marker.
|
|
FSlateDrawElement::MakeLines
|
|
(
|
|
PaintState->OutDrawElements,
|
|
PaintState->LayerId,
|
|
PaintState->AllottedGeometry.ToPaintGeometry(),
|
|
LinePoints,
|
|
PaintState->DrawEffects,
|
|
PaintState->WidgetStyle.GetColorAndOpacityTint() * FColorList::SkyBlue,
|
|
false
|
|
);
|
|
}
|
|
}
|
|
|
|
PaintState->LayerId++;
|
|
|
|
const double PositionXStartPx = FMath::TruncToFloat( PositionXMS*NumPixelsPerMillisecond / (double)NUM_PIXELS_BETWEEN_TIMELINE )*(double)NUM_PIXELS_BETWEEN_TIMELINE;
|
|
const double PositionXEndPx = PositionXStartPx + RangeXMS*NumPixelsPerMillisecond;
|
|
|
|
for( double TimelinePosXPx = PositionXStartPx; TimelinePosXPx < PositionXEndPx; TimelinePosXPx += (double)NUM_PIXELS_BETWEEN_TIMELINE )
|
|
{
|
|
LinePoints.Reset( 2 );
|
|
LinePoints.Add( FVector2D( TimelinePosXPx - ThreadViewOffsetPx, 0.0f ) );
|
|
LinePoints.Add( FVector2D( TimelinePosXPx - ThreadViewOffsetPx, PaintState->Size2D().Y ) );
|
|
|
|
// Draw time line.
|
|
FSlateDrawElement::MakeLines
|
|
(
|
|
PaintState->OutDrawElements,
|
|
PaintState->LayerId,
|
|
PaintState->AllottedGeometry.ToPaintGeometry(),
|
|
LinePoints,
|
|
PaintState->DrawEffects,
|
|
PaintState->WidgetStyle.GetColorAndOpacityTint() * FColorList::LimeGreen,
|
|
false
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
void SProfilerThreadView::DrawUIStackNodes() const
|
|
{
|
|
// SCOPE_LOG_TIME_FUNC();
|
|
check( PaintState );
|
|
const double ThreadViewOffsetPx = PositionXMS*NumPixelsPerMillisecond;
|
|
PaintState->LayerId++;
|
|
|
|
static const FSlateBrush* BorderBrush = FAppStyle::GetBrush( "Profiler.ThreadView.SampleBorder" );
|
|
const FColor GameThreadColor = FColorList::Red;
|
|
const FColor RenderThreadColor = FColorList::Blue;
|
|
const FColor ThreadColors[2] = {GameThreadColor, RenderThreadColor};
|
|
|
|
// Draw nodes.
|
|
for( const auto& RowOfNodes : ProfilerUIStream.LinearRowsOfNodes )
|
|
{
|
|
int32 NodeIndex = 0;
|
|
for( const auto& UIStackNode : RowOfNodes )
|
|
{
|
|
NodeIndex++;
|
|
// Check if the node is visible.
|
|
//if( UIStackNode->IsVisible() )
|
|
{
|
|
const FVector2D PositionPx = UIStackNode->GetLocalPosition( ThreadViewOffsetPx, PositionY ) * FVector2D( 1.0f, NUM_PIXELS_PER_ROW );
|
|
const FVector2D SizePx = FVector2D( FMath::Max( UIStackNode->WidthPx - 1.0, 0.0 ), NUM_PIXELS_PER_ROW );
|
|
const FSlateRect ClippedNodeRect = PaintState->LocalClippingRect.IntersectionWith( FSlateRect( PositionPx, PositionPx + SizePx ) );
|
|
|
|
// Check if this node is inside the visible area.
|
|
if( ClippedNodeRect.IsEmpty() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FColor NodeColor = UIStackNode->bIsCombined ? ThreadColors[UIStackNode->ThreadIndex].WithAlpha( 64 ) : ThreadColors[UIStackNode->ThreadIndex].WithAlpha( 192 );
|
|
NodeColor.G += (NodeIndex % 2) ? 0 : 64;
|
|
|
|
// Draw a cycle counter for this profiler UI stack node.
|
|
FSlateDrawElement::MakeBox
|
|
(
|
|
PaintState->OutDrawElements,
|
|
PaintState->LayerId,
|
|
PaintState->AllottedGeometry.ToPaintGeometry( ClippedNodeRect.GetSize(), FSlateLayoutTransform(ClippedNodeRect.GetTopLeft()) ),
|
|
BorderBrush,
|
|
PaintState->DrawEffects,
|
|
NodeColor
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// #Profiler: 2014-04-29 Separate layer for makebox, makeshadowtext, maketext.
|
|
PaintState->LayerId++;
|
|
|
|
const float MarkerPosYOffsetPx = ((float)NUM_PIXELS_PER_ROW - PaintState->SummaryFont8Height)*0.5f;
|
|
|
|
// Draw nodes' descriptions.
|
|
for( const auto& RowOfNodes : ProfilerUIStream.LinearRowsOfNodes )
|
|
{
|
|
for( const auto& UIStackNode : RowOfNodes )
|
|
{
|
|
const FVector2D PositionPx = UIStackNode->GetLocalPosition( ThreadViewOffsetPx, PositionY ) * FVector2D( 1.0f, NUM_PIXELS_PER_ROW );
|
|
const FVector2D SizePx = FVector2D( UIStackNode->WidthPx, NUM_PIXELS_PER_ROW );
|
|
const FSlateRect ClippedNodeRect = PaintState->LocalClippingRect.IntersectionWith( FSlateRect( PositionPx, PositionPx + SizePx ) );
|
|
|
|
// Check if this node is inside the visible area.
|
|
if( ClippedNodeRect.IsEmpty() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FString StringStatName = UIStackNode->StatName.GetPlainNameString();
|
|
FString StringStatNameWithTime = StringStatName + FString::Printf( TEXT( " (%.4f MS)" ), UIStackNode->GetDurationMS() );
|
|
if( UIStackNode->bIsCulled )
|
|
{
|
|
StringStatName += TEXT( " [C]" );
|
|
StringStatNameWithTime += TEXT( " [C]" );
|
|
}
|
|
|
|
// Update position of the text to be always visible and try to center it.
|
|
const float StatNameWidthPx = static_cast<float>(PaintState->FontMeasureService->Measure( StringStatName, PaintState->SummaryFont8 ).X);
|
|
const float StatNameWithTimeWidthPx = static_cast<float>(PaintState->FontMeasureService->Measure( StringStatNameWithTime, PaintState->SummaryFont8 ).X);
|
|
const float TextAreaWidthPx = ClippedNodeRect.GetSize().X;
|
|
|
|
bool bUseShortVersion = true;
|
|
FVector2D AdjustedPositionPx;
|
|
// Center the stat name with timing if we can.
|
|
if( TextAreaWidthPx > StatNameWithTimeWidthPx )
|
|
{
|
|
AdjustedPositionPx = FVector2D( ClippedNodeRect.Left + (TextAreaWidthPx - StatNameWithTimeWidthPx)*0.5f, PositionPx.Y + MarkerPosYOffsetPx );
|
|
bUseShortVersion = false;
|
|
}
|
|
// Center the stat name.
|
|
else if( TextAreaWidthPx > StatNameWidthPx )
|
|
{
|
|
AdjustedPositionPx = FVector2D( ClippedNodeRect.Left + (TextAreaWidthPx - StatNameWidthPx)*0.5f, PositionPx.Y + MarkerPosYOffsetPx );
|
|
}
|
|
// Move to the edge.
|
|
else
|
|
{
|
|
AdjustedPositionPx = FVector2D( ClippedNodeRect.Left, PositionPx.Y + MarkerPosYOffsetPx );
|
|
}
|
|
|
|
const FVector2D AbsolutePositionPx = PaintState->AllottedGeometry.LocalToAbsolute( ClippedNodeRect.GetTopLeft() );
|
|
const FSlateRect AbsoluteClippingRect = FSlateRect( AbsolutePositionPx, AbsolutePositionPx + ClippedNodeRect.GetSize() );
|
|
|
|
DrawText( bUseShortVersion ? StringStatName : StringStatNameWithTime, PaintState->SummaryFont8, AdjustedPositionPx, FColorList::White, FColorList::Black, FVector2D( 1.0f, 1.0f ), &AbsoluteClippingRect );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SProfilerThreadView::DrawFrameMarkers() const
|
|
{
|
|
check( PaintState );
|
|
const double ThreadViewOffsetPx = PositionXMS*NumPixelsPerMillisecond;
|
|
PaintState->LayerId++;
|
|
|
|
for( const auto& ThreadNode : ProfilerUIStream.ThreadNodes )
|
|
{
|
|
if( ThreadNode.StatName == NAME_GameThread )
|
|
{
|
|
const double MarkerPosXPx = ThreadNode.GetLocalPosition( ThreadViewOffsetPx, 0.0f ).X + ThreadNode.WidthPx;
|
|
|
|
// Check if this frame time marker is inside the visible area.
|
|
if( MarkerPosXPx < 0.0 || MarkerPosXPx > PaintState->Size2D().X )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Draw text.
|
|
const FString FrameIndexStr = FString::Printf( TEXT( "%i" ), ThreadNode.FrameIndex );
|
|
const FString FrameTimesStr = FString::Printf( TEXT( "%.4f [%.4f] MS" ), ThreadNode.CycleCountersEndTimeMS, ThreadNode.GetDurationMS() );
|
|
|
|
double MarkerPosYPx = PaintState->Size2D().Y - 2 * PaintState->SummaryFont8Height;
|
|
DrawText( FrameIndexStr, PaintState->SummaryFont8, FVector2D( MarkerPosXPx, MarkerPosYPx ), FColorList::SkyBlue, FColorList::Black, FVector2D( 1.0f, 1.0f ) );
|
|
|
|
MarkerPosYPx += PaintState->SummaryFont8Height;
|
|
DrawText( FrameTimesStr, PaintState->SummaryFont8, FVector2D( MarkerPosXPx, MarkerPosYPx ), FColorList::SkyBlue, FColorList::Black, FVector2D( 1.0f, 1.0f ) );
|
|
}
|
|
}
|
|
|
|
PaintState->LayerId++;
|
|
|
|
const double PositionXStartPx = FMath::TruncToFloat( PositionXMS*NumPixelsPerMillisecond / (double)NUM_PIXELS_BETWEEN_TIMELINE )*(double)NUM_PIXELS_BETWEEN_TIMELINE;
|
|
const double PositionXEndPx = PositionXStartPx + RangeXMS*NumPixelsPerMillisecond;
|
|
|
|
for( double TimelinePosXPx = PositionXStartPx; TimelinePosXPx < PositionXEndPx; TimelinePosXPx += (double)NUM_PIXELS_BETWEEN_TIMELINE )
|
|
{
|
|
const FString TimelineStr = FString::Printf( TEXT( "%.4f MS" ), TimelinePosXPx / NumPixelsPerMillisecond );
|
|
|
|
// Draw time line text.
|
|
double MarkerPosYPx = PaintState->Size2D().Y - 3 * PaintState->SummaryFont8Height;
|
|
DrawText( TimelineStr, PaintState->SummaryFont8, FVector2D( TimelinePosXPx - ThreadViewOffsetPx, MarkerPosYPx ), FColorList::LimeGreen, FColorList::Black, FVector2D( 1.0f, 1.0f ) );
|
|
}
|
|
|
|
#if 0
|
|
for( int32 FrameIndex = FramesIndices.X; FrameIndex < FramesIndices.Y; ++FrameIndex )
|
|
{
|
|
const double FrameTimeMS = ProfilerStream->GetFrameTimeMS( FrameIndex );
|
|
const double ElapsedTimeMS = ProfilerStream->GetElapsedFrameTimeMS( FrameIndex );
|
|
const double FrameEndMarkerLocalPosX = ElapsedTimeMS * NumPixelsPerMillisecond - ThreadViewOffsetPx;
|
|
|
|
// Draw text.
|
|
const FVector2D TextLocalPosTop = FVector2D( FrameEndMarkerLocalPosX, PaintState->SummaryFont8Height );
|
|
const FString FrameTimeStr = FString::Printf( TEXT( "%.4f [%.4f] MS" ), ElapsedTimeMS, FrameTimeMS );
|
|
DrawText( FrameTimeStr, PaintState->SummaryFont8, TextLocalPosTop, FColorList::SkyBlue, FColorList::Black, FVector2D( 1.0f, 1.0f ) );
|
|
|
|
const FVector2D TextLocalPosBottom = FVector2D( FrameEndMarkerLocalPosX, PaintState->Size2D().Y - 2 * PaintState->SummaryFont8Height );
|
|
DrawText( FrameTimeStr, PaintState->SummaryFont8, TextLocalPosBottom, FColorList::SkyBlue, FColorList::Black, FVector2D( 1.0f, 1.0f ) );
|
|
}
|
|
#endif // 0
|
|
}
|
|
|
|
|
|
void SProfilerThreadView::DrawUIStackNodes_Recursively( const FProfilerUIStackNode& UIStackNode ) const
|
|
{
|
|
// OBSOLETE
|
|
check( PaintState );
|
|
// Don't render thread nodes.
|
|
if( UIStackNode.ThreadIndex != FProfilerUIStackNode::THREAD_NODE_INDEX )
|
|
{
|
|
static const FSlateColorBrush SolidWhiteBrush = FSlateColorBrush( FColorList::White );
|
|
const FColor GameThreadColor = FColorList::Red;
|
|
|
|
const FVector2D Position = FVector2D( UIStackNode.PositionXPx /*- PositionXMS*/, UIStackNode.PositionY*(double)NUM_PIXELS_PER_ROW );
|
|
const FVector2D Size = FVector2D( UIStackNode.WidthPx, NUM_PIXELS_PER_ROW );
|
|
|
|
// Draw a cycle counter for this profiler UI stack node.
|
|
FSlateDrawElement::MakeBox
|
|
(
|
|
PaintState->OutDrawElements,
|
|
PaintState->LayerId,
|
|
PaintState->AllottedGeometry.ToPaintGeometry( Size, FSlateLayoutTransform(Position) ),
|
|
&SolidWhiteBrush,
|
|
PaintState->DrawEffects,
|
|
GameThreadColor
|
|
);
|
|
|
|
const FString StringStatName = UIStackNode.StatName.GetPlainNameString();
|
|
DrawText( UIStackNode.StatName.GetPlainNameString(), PaintState->SummaryFont8, Position, FColorList::White, FColorList::Black, FVector2D( 1.0f, 1.0f ) );
|
|
}
|
|
|
|
for( const auto& UIStackNodeChild : UIStackNode.Children )
|
|
{
|
|
DrawUIStackNodes_Recursively( UIStackNodeChild );
|
|
}
|
|
}
|
|
|
|
|
|
void SProfilerThreadView::DrawText( const FString& Text, const FSlateFontInfo& FontInfo, FVector2D Position, const FColor& TextColor, const FColor& ShadowColor, FVector2D ShadowOffset, const FSlateRect* ClippingRect /*= nullptr*/ ) const
|
|
{
|
|
check( PaintState );
|
|
|
|
if( ShadowOffset.SizeSquared() > 0.0f )
|
|
{
|
|
FSlateDrawElement::MakeText
|
|
(
|
|
PaintState->OutDrawElements,
|
|
PaintState->LayerId,
|
|
PaintState->AllottedGeometry.ToOffsetPaintGeometry( Position + ShadowOffset ),
|
|
Text,
|
|
FontInfo,
|
|
PaintState->DrawEffects,
|
|
ShadowColor
|
|
);
|
|
}
|
|
|
|
FSlateDrawElement::MakeText
|
|
(
|
|
PaintState->OutDrawElements,
|
|
++PaintState->LayerId,
|
|
PaintState->AllottedGeometry.ToOffsetPaintGeometry( Position ),
|
|
Text,
|
|
FontInfo,
|
|
PaintState->DrawEffects,
|
|
TextColor
|
|
);
|
|
}
|
|
|
|
|
|
FReply SProfilerThreadView::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
FReply Reply = FReply::Unhandled();
|
|
|
|
if( IsReady() )
|
|
{
|
|
MousePositionOnButtonDown = MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() );
|
|
|
|
if( MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton )
|
|
{
|
|
bIsLeftMousePressed = true;
|
|
DistanceDragged = PositionXMS;
|
|
|
|
// Capture mouse, so we can move outside this widget.
|
|
Reply = FReply::Handled().CaptureMouse( SharedThis( this ) );
|
|
}
|
|
else if( MouseEvent.GetEffectingButton() == EKeys::RightMouseButton )
|
|
{
|
|
bIsRightMousePressed = true;
|
|
}
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
|
|
FReply SProfilerThreadView::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
FReply Reply = FReply::Unhandled();
|
|
|
|
if( IsReady() )
|
|
{
|
|
const FVector2D MousePositionOnButtonUp = MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() );
|
|
const bool bIsValidForMouseClick = MousePositionOnButtonUp.Equals( MousePositionOnButtonDown, MOUSE_SNAP_DISTANCE );
|
|
|
|
if( MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton )
|
|
{
|
|
if( bIsLeftMousePressed )
|
|
{
|
|
// Release the mouse, we are no longer dragging.
|
|
Reply = FReply::Handled().ReleaseMouseCapture();
|
|
}
|
|
|
|
bIsLeftMousePressed = false;
|
|
}
|
|
else if( MouseEvent.GetEffectingButton() == EKeys::RightMouseButton )
|
|
{
|
|
if( bIsRightMousePressed )
|
|
{
|
|
if( bIsValidForMouseClick )
|
|
{
|
|
ShowContextMenu( MouseEvent.GetScreenSpacePosition() );
|
|
Reply = FReply::Handled();
|
|
}
|
|
}
|
|
bIsRightMousePressed = false;
|
|
}
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
|
|
FReply SProfilerThreadView::OnMouseMove( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
// SCOPE_LOG_TIME_FUNC();
|
|
|
|
FReply Reply = FReply::Unhandled();
|
|
|
|
if( IsReady() )
|
|
{
|
|
const FVector2D LocalMousePosition = MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() );
|
|
|
|
HoveredPositionX = 0.0;//PositionToFrameIndex( LocalMousePosition.X );
|
|
HoveredPositionY = 0.0;
|
|
|
|
const double CursorPosXDelta = -MouseEvent.GetCursorDelta().X;
|
|
const double ScrollSpeed = 1.0 / ZoomFactorX;
|
|
|
|
if( MouseEvent.IsMouseButtonDown( EKeys::LeftMouseButton ) )
|
|
{
|
|
if( HasMouseCapture() && !MouseEvent.GetCursorDelta().IsZero() )
|
|
{
|
|
DistanceDragged += CursorPosXDelta*ScrollSpeed*0.1;
|
|
|
|
// Inform other widgets that we have scrolled the thread-view.
|
|
SetPositionX( FMath::Clamp( DistanceDragged, 0.0, TotalRangeXMS - RangeXMS ) );
|
|
CursorType = EThreadViewCursor::Hand;
|
|
Reply = FReply::Handled();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CursorType = EThreadViewCursor::Default;
|
|
}
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
|
|
void SProfilerThreadView::OnMouseEnter( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
|
|
}
|
|
|
|
void SProfilerThreadView::OnMouseLeave( const FPointerEvent& MouseEvent )
|
|
{
|
|
if( !HasMouseCapture() )
|
|
{
|
|
bIsLeftMousePressed = false;
|
|
bIsRightMousePressed = false;
|
|
|
|
CursorType = EThreadViewCursor::Default;
|
|
}
|
|
}
|
|
|
|
FReply SProfilerThreadView::OnMouseWheel( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
FReply Reply = FReply::Unhandled();
|
|
|
|
const bool bZoomIn = MouseEvent.GetWheelDelta() < 0.0f;
|
|
const double Center = PositionXMS + RangeXMS*0.5f;
|
|
|
|
const double MinVisibleRangeMS = 1.0f / (double)INV_MIN_VISIBLE_RANGE_X;
|
|
const double NewUnclampedRange = bZoomIn ? RangeXMS*1.25f : RangeXMS / 1.25f;
|
|
const double NewRange = FMath::Clamp( NewUnclampedRange, MinVisibleRangeMS, FMath::Min( TotalRangeXMS, (double)MAX_VISIBLE_RANGE_X ) );
|
|
|
|
const double NewPositionX = FMath::Clamp( Center, NewRange*0.5f, TotalRangeXMS - NewRange*0.5f ) - NewRange*0.5f;
|
|
SetTimeRange( NewPositionX, NewPositionX + NewRange );
|
|
|
|
return Reply;
|
|
}
|
|
|
|
FReply SProfilerThreadView::OnMouseButtonDoubleClick( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
FReply Reply = FReply::Unhandled();
|
|
return Reply;
|
|
}
|
|
|
|
|
|
FCursorReply SProfilerThreadView::OnCursorQuery( const FGeometry& MyGeometry, const FPointerEvent& CursorEvent ) const
|
|
{
|
|
FCursorReply CursorReply = FCursorReply::Unhandled();
|
|
if( CursorType == EThreadViewCursor::Arrow )
|
|
{
|
|
CursorReply = FCursorReply::Cursor( EMouseCursor::ResizeLeftRight );
|
|
}
|
|
else if( CursorType == EThreadViewCursor::Hand )
|
|
{
|
|
CursorReply = FCursorReply::Cursor( EMouseCursor::GrabHand );
|
|
}
|
|
return CursorReply;
|
|
}
|
|
|
|
void SProfilerThreadView::ShowContextMenu( const FVector2D& ScreenSpacePosition )
|
|
{
|
|
|
|
}
|
|
|
|
void SProfilerThreadView::BindCommands()
|
|
{
|
|
|
|
}
|
|
|
|
|
|
/* SProfilerThreadView interface
|
|
*****************************************************************************/
|
|
|
|
void SProfilerThreadView::SetPositionXToByScrollBar( double ScrollOffset )
|
|
{
|
|
SetPositionX( ScrollOffset*TotalRangeXMS );
|
|
}
|
|
|
|
|
|
void SProfilerThreadView::SetPositionX( double NewPositionXMS )
|
|
{
|
|
const double ClampedPositionXMS = FMath::Clamp( NewPositionXMS, 0.0, TotalRangeXMS - RangeXMS );
|
|
SetTimeRange( ClampedPositionXMS, ClampedPositionXMS + RangeXMS, true );
|
|
}
|
|
|
|
|
|
void SProfilerThreadView::SetPositonYTo( double ScrollOffset )
|
|
{
|
|
|
|
}
|
|
|
|
|
|
void SProfilerThreadView::SetTimeRange( double StartTimeMS, double EndTimeMS, bool bBroadcast )
|
|
{
|
|
check( EndTimeMS > StartTimeMS );
|
|
|
|
PositionXMS = StartTimeMS;
|
|
RangeXMS = EndTimeMS - StartTimeMS;
|
|
FramesIndices = ProfilerStream->GetFramesIndicesForTimeRange( StartTimeMS, EndTimeMS );
|
|
|
|
bUpdateData = true;
|
|
|
|
//UE_LOG( LogTemp, Log, TEXT( "StartTimeMS=%f, EndTimeMS=%f, bBroadcast=%1i FramesIndices=%3i,%3i" ), StartTimeMS, EndTimeMS, (int)bBroadcast, FramesIndices.X, FramesIndices.Y );
|
|
if( bBroadcast )
|
|
{
|
|
ViewPositionXChangedEvent.Broadcast( StartTimeMS, EndTimeMS, TotalRangeXMS, FramesIndices.X, FramesIndices.Y );
|
|
}
|
|
}
|
|
|
|
|
|
void SProfilerThreadView::SetFrameRange(int32 FrameStart, int32 FrameEnd)
|
|
{
|
|
const double EndTimeMS = ProfilerStream->GetElapsedFrameTimeMS(FrameEnd);
|
|
const double StartTimeMS = ProfilerStream->GetElapsedFrameTimeMS(FrameStart) - ProfilerStream->GetFrameTimeMS(FrameStart);
|
|
SetTimeRange(StartTimeMS, EndTimeMS, true);
|
|
}
|
|
|
|
|
|
void SProfilerThreadView::AttachProfilerStream(const FProfilerStream& InProfilerStream)
|
|
{
|
|
ProfilerStream = &InProfilerStream;
|
|
|
|
TotalRangeXMS = ProfilerStream->GetElapsedTime();
|
|
TotalRangeY = ProfilerStream->GetNumThreads()*FProfilerUIStream::DEFAULT_VISIBLE_THREAD_DEPTH;
|
|
|
|
// Display the first frame.
|
|
const FProfilerFrame* ProfilerFrame = ProfilerStream->GetProfilerFrame(0);
|
|
SetTimeRange(ProfilerFrame->Root->CycleCounterStartTimeMS, ProfilerFrame->Root->CycleCounterEndTimeMS);
|
|
}
|
|
|
|
|
|
/* SProfilerThreadView implementation
|
|
*****************************************************************************/
|
|
|
|
void SProfilerThreadView::ProcessData()
|
|
{
|
|
// SCOPE_LOG_TIME_FUNC();
|
|
|
|
ProfilerUIStream.GenerateUIStream( *ProfilerStream, PositionXMS, PositionXMS + RangeXMS, ZoomFactorX, NumMillisecondsPerWindow, NumPixelsPerMillisecond, NumMillisecondsPerSample );
|
|
}
|
|
|
|
|
|
bool SProfilerThreadView::IsReady() const
|
|
{
|
|
return ProfilerStream && ProfilerStream->GetNumFrames() > 0;
|
|
}
|
|
|
|
|
|
bool SProfilerThreadView::ShouldUpdateData()
|
|
{
|
|
return bUpdateData;
|
|
}
|
|
|
|
|
|
void SProfilerThreadView::UpdateInternalConstants()
|
|
{
|
|
ZoomFactorX = (double)NUM_MILLISECONDS_PER_WINDOW / RangeXMS;
|
|
RangeY = FMath::RoundToFloat(ThisGeometry.GetLocalSize().Y / (double)NUM_PIXELS_PER_ROW);
|
|
|
|
const double Aspect = ThisGeometry.GetLocalSize().X / (double)NUM_MILLISECONDS_PER_WINDOW * ZoomFactorX;
|
|
NumMillisecondsPerWindow = (double)ThisGeometry.GetLocalSize().X / Aspect;
|
|
NumPixelsPerMillisecond = (double)ThisGeometry.GetLocalSize().X / NumMillisecondsPerWindow;
|
|
NumMillisecondsPerSample = NumMillisecondsPerWindow / (double)ThisGeometry.GetLocalSize().X * (double)MIN_NUM_PIXELS_PER_SAMPLE;
|
|
}
|
|
|
|
#endif // STATS
|