// 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 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(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(PaintState->FontMeasureService->Measure( StringStatName, PaintState->SummaryFont8 ).X); const float StatNameWithTimeWidthPx = static_cast(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