// Copyright Epic Games, Inc. All Rights Reserved. #include "STimingView.h" #include "Containers/ArrayBuilder.h" #include "Containers/MapBuilder.h" #include "EditorStyleSet.h" #include "Features/IModularFeatures.h" #include "Fonts/FontMeasure.h" #include "Fonts/SlateFontInfo.h" #include "Framework/Application/MenuStack.h" #include "Framework/Application/SlateApplication.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "HAL/PlatformTime.h" #include "Layout/WidgetPath.h" #include "Misc/Paths.h" #include "Rendering/DrawElements.h" #include "SlateOptMacros.h" #include "Styling/CoreStyle.h" #include "Styling/SlateBrush.h" #include "TraceServices/AnalysisService.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Layout/SScrollBar.h" #include "Widgets/Layout/SSpacer.h" #include "Widgets/SOverlay.h" #include "Widgets/Text/STextBlock.h" // Desktop Platform #include "DesktopPlatformModule.h" #include "IDesktopPlatform.h" #include "Insights/ViewModels/ThreadTrackEvent.h" // Insights #include "Insights/Common/PaintUtils.h" #include "Insights/Common/Stopwatch.h" #include "Insights/Common/TimeUtils.h" #include "Insights/ITimingViewExtender.h" #include "Insights/InsightsManager.h" #include "Insights/InsightsStyle.h" #include "Insights/LoadingProfiler/LoadingProfilerManager.h" #include "Insights/LoadingProfiler/Widgets/SLoadingProfilerWindow.h" #include "Insights/Table/Widgets/STableTreeView.h" #include "Insights/Tests/TimingProfilerTests.h" #include "Insights/TimingProfilerCommon.h" #include "Insights/TimingProfilerManager.h" #include "Insights/ViewModels/BaseTimingTrack.h" #include "Insights/ViewModels/DrawHelpers.h" #include "Insights/ViewModels/FileActivityTimingTrack.h" #include "Insights/ViewModels/FrameTimingTrack.h" #include "Insights/ViewModels/GraphSeries.h" #include "Insights/ViewModels/GraphTrack.h" #include "Insights/ViewModels/LoadingTimingTrack.h" #include "Insights/ViewModels/MarkersTimingTrack.h" #include "Insights/ViewModels/MinimalTimerExporter.h" #include "Insights/ViewModels/ThreadTimingTrack.h" #include "Insights/ViewModels/TimeRulerTrack.h" #include "Insights/ViewModels/TimingEventSearch.h" #include "Insights/ViewModels/TimingGraphTrack.h" #include "Insights/ViewModels/TimingViewDrawHelper.h" #include "Insights/Widgets/SStatsView.h" #include "Insights/Widgets/STimersView.h" #include "Insights/Widgets/STimingProfilerWindow.h" #include "Insights/Widgets/STimingViewTrackList.h" #include //////////////////////////////////////////////////////////////////////////////////////////////////// #define LOCTEXT_NAMESPACE "STimingView" #define ACTIVATE_BENCHMARK 0 // start auto generated ids from a big number (MSB set to 1) to avoid collisions with ids for gpu/cpu tracks based on 32bit timeline index uint64 FBaseTimingTrack::IdGenerator = (1ULL << 63); const TCHAR* GetFileActivityTypeName(Trace::EFileActivityType Type); uint32 GetFileActivityTypeColor(Trace::EFileActivityType Type); namespace Insights { const FName TimingViewExtenderFeatureName(TEXT("TimingViewExtender")); } //////////////////////////////////////////////////////////////////////////////////////////////////// STimingView::STimingView() : bScrollableTracksOrderIsDirty(false), FrameSharedState(MakeShared(this)), ThreadTimingSharedState(MakeShared(this)), LoadingSharedState(MakeShared(this)), bAssetLoadingMode(false), FileActivitySharedState(MakeShared(this)), TimeRulerTrack(MakeShared()), DefaultTimeMarker(MakeShared()), MarkersTrack(MakeShared()), GraphTrack(MakeShared()), WhiteBrush(FInsightsStyle::Get().GetBrush("WhiteBrush")), MainFont(FCoreStyle::GetDefaultFontStyle("Regular", 8)) { DefaultTimeMarker->SetName(TEXT("")); DefaultTimeMarker->SetColor(FLinearColor(0.85f, 0.5f, 0.03f, 0.5f)); GraphTrack->SetName(TEXT("Main Graph Track")); IModularFeatures::Get().RegisterModularFeature(Insights::TimingViewExtenderFeatureName, FrameSharedState.Get()); IModularFeatures::Get().RegisterModularFeature(Insights::TimingViewExtenderFeatureName, ThreadTimingSharedState.Get()); IModularFeatures::Get().RegisterModularFeature(Insights::TimingViewExtenderFeatureName, LoadingSharedState.Get()); IModularFeatures::Get().RegisterModularFeature(Insights::TimingViewExtenderFeatureName, FileActivitySharedState.Get()); ExtensionOverlay = SNew(SOverlay).Visibility(EVisibility::SelfHitTestInvisible); } //////////////////////////////////////////////////////////////////////////////////////////////////// STimingView::~STimingView() { AllTracks.Reset(); TopDockedTracks.Reset(); BottomDockedTracks.Reset(); ScrollableTracks.Reset(); ForegroundTracks.Reset(); for (Insights::ITimingViewExtender* Extender: GetExtenders()) { Extender->OnEndSession(*this); } IModularFeatures::Get().UnregisterModularFeature(Insights::TimingViewExtenderFeatureName, FileActivitySharedState.Get()); IModularFeatures::Get().UnregisterModularFeature(Insights::TimingViewExtenderFeatureName, LoadingSharedState.Get()); IModularFeatures::Get().UnregisterModularFeature(Insights::TimingViewExtenderFeatureName, ThreadTimingSharedState.Get()); IModularFeatures::Get().UnregisterModularFeature(Insights::TimingViewExtenderFeatureName, FrameSharedState.Get()); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::Construct(const FArguments& InArgs) { ChildSlot [SNew(SOverlay) .Visibility(EVisibility::SelfHitTestInvisible) + SOverlay::Slot() .VAlign(VAlign_Bottom) .Padding(FMargin(0, 0, 12, 0)) [SAssignNew(HorizontalScrollBar, SScrollBar) .Orientation(Orient_Horizontal) .AlwaysShowScrollbar(false) .Visibility(EVisibility::Visible) .Thickness(FVector2D(5.0f, 5.0f)) .OnUserScrolled(this, &STimingView::HorizontalScrollBar_OnUserScrolled)] + SOverlay::Slot() .HAlign(HAlign_Right) .Padding(FMargin(0.0f, 0.0f, 0.0f, 0.0f)) [SAssignNew(VerticalScrollBar, SScrollBar) .Orientation(Orient_Vertical) .AlwaysShowScrollbar(false) .Visibility(EVisibility::Visible) .Thickness(FVector2D(5.0f, 5.0f)) .OnUserScrolled(this, &STimingView::VerticalScrollBar_OnUserScrolled)] + SOverlay::Slot() .HAlign(HAlign_Left) .VAlign(VAlign_Top) .Padding(FMargin(0.0f, 0.0f, 0.0f, 0.0f)) [ // Tracks Filter SNew(SComboButton) .ComboButtonStyle(FEditorStyle::Get(), "GenericFilters.ComboButtonStyle") .ForegroundColor(FLinearColor::White) .ToolTipText(LOCTEXT("TracksFilterToolTip", "Filter timing tracks.")) .OnGetMenuContent(this, &STimingView::MakeTracksFilterMenu) .HasDownArrow(true) .ContentPadding(FMargin(1.0f, 1.0f, 1.0f, 1.0f)) .ButtonContent() [SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(0.0f) .AutoWidth() [SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "GenericFilters.TextStyle") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.9")) .Text(FText::FromString(FString(TEXT("\xf0b0"))) /*fa-filter*/)] + SHorizontalBox::Slot() .Padding(FMargin(2.0f, 0.0f, 0.0f, 0.0f)) .AutoWidth() [SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "GenericFilters.TextStyle") .Text(LOCTEXT("TracksFilter", "Tracks"))]]] + SOverlay::Slot() .HAlign(HAlign_Right) .VAlign(VAlign_Top) .Padding(FMargin(0.0f, 0.0f, 0.0f, 0.0f)) [SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(0.0f) .AutoWidth() [SNew(SCheckBox) .Style(FEditorStyle::Get(), "ToggleButtonCheckbox") .HAlign(HAlign_Center) .Padding(FMargin(0.0f, 2.0f, 1.0f, 4.0f)) .OnCheckStateChanged(this, &STimingView::AutoScroll_OnCheckStateChanged) .IsChecked(this, &STimingView::AutoScroll_IsChecked) .ToolTipText(LOCTEXT("AutoScroll_Tooltip", "Auto Scroll")) [SNew(STextBlock) .Text(LOCTEXT("AutoScroll_Button", " >> ")) .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Caption"))]] + SHorizontalBox::Slot() .Padding(0.0f) .AutoWidth() [SNew(SComboButton) .ComboButtonStyle(FEditorStyle::Get(), "GenericFilters.ComboButtonStyle") .ForegroundColor(FLinearColor::White) //.ToolTipText(LOCTEXT("AutoScrollOptions_ToolTip", "Auto-scroll options")) .OnGetMenuContent(this, &STimingView::MakeAutoScrollOptionsMenu) .HasDownArrow(true) .ContentPadding(FMargin(0.0f, 0.0f, 0.0f, 0.0f)) .ButtonContent() [SNew(SBorder) .Padding(0.0f)]]] + SOverlay::Slot() .HAlign(HAlign_Fill) .VAlign(VAlign_Fill) .Padding(FMargin(0.0f, 0.0f, 0.0f, 0.0f)) [ExtensionOverlay.ToSharedRef()]]; UpdateHorizontalScrollBar(); UpdateVerticalScrollBar(); BindCommands(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::HideAllDefaultTracks() { FrameSharedState->HideAllFrameTracks(); ThreadTimingSharedState->HideAllGpuTracks(); ThreadTimingSharedState->HideAllCpuTracks(); LoadingSharedState->HideAllLoadingTracks(); FileActivitySharedState->HideAllIoTracks(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::Reset(bool bIsFirstReset) { if (!bIsFirstReset) { for (Insights::ITimingViewExtender* Extender: GetExtenders()) { Extender->OnEndSession(*this); } } ////////////////////////////////////////////////// Viewport.Reset(); ////////////////////////////////////////////////// for (auto& KV: AllTracks) { KV.Value->SetLocation(ETimingTrackLocation::None); } AllTracks.Reset(); TopDockedTracks.Reset(); BottomDockedTracks.Reset(); ScrollableTracks.Reset(); ForegroundTracks.Reset(); bScrollableTracksOrderIsDirty = false; FTimingEventsTrack::bUseDownSampling = true; ////////////////////////////////////////////////// TimeRulerTrack->Reset(); AddTopDockedTrack(TimeRulerTrack); TimeRulerTrack->AddTimeMarker(DefaultTimeMarker); SetTimeMarker(std::numeric_limits::infinity()); #if 0 // test for multiple time markers TSharedRef TimeMarkerA = DefaultTimeMarker; TimeMarkerA->SetName(TEXT("A")); TimeMarkerA->SetColor(FLinearColor(0.85f, 0.5f, 0.03f, 0.5f)); TSharedRef TimeMarkerB = MakeShared(); TimeRulerTrack->AddTimeMarker(TimeMarkerB); TimeMarkerB->SetName(TEXT("B")); TimeMarkerB->SetColor(FLinearColor(0.03f, 0.85f, 0.5f, 0.5f)); TSharedRef TimeMarkerC = MakeShared(); TimeRulerTrack->AddTimeMarker(TimeMarkerC); TimeMarkerC->SetName(TEXT("C")); TimeMarkerC->SetColor(FLinearColor(0.03f, 0.5f, 0.85f, 0.5f)); TimeMarkerA->SetTime(0.0f); TimeMarkerB->SetTime(1.0f); TimeMarkerC->SetTime(2.0f); #endif MarkersTrack->Reset(); AddTopDockedTrack(MarkersTrack); GraphTrack->Reset(); GraphTrack->SetOrder(FTimingTrackOrder::First); constexpr double GraphTrackHeight = 200.0; GraphTrack->SetHeight(static_cast(GraphTrackHeight)); GraphTrack->GetSharedValueViewport().SetBaselineY(GraphTrackHeight - 1.0); GraphTrack->GetSharedValueViewport().SetScaleY(GraphTrackHeight / 0.1); // 100ms GraphTrack->AddDefaultFrameSeries(); GraphTrack->SetVisibilityFlag(false); AddTopDockedTrack(GraphTrack); ////////////////////////////////////////////////// ExtensionOverlay->ClearChildren(); ////////////////////////////////////////////////// MousePosition = FVector2D::ZeroVector; MousePositionOnButtonDown = FVector2D::ZeroVector; ViewportStartTimeOnButtonDown = 0.0; ViewportScrollPosYOnButtonDown = 0.0f; MousePositionOnButtonUp = FVector2D::ZeroVector; LastScrollPosY = 0.0f; bIsLMB_Pressed = false; bIsRMB_Pressed = false; bIsSpaceBarKeyPressed = false; bIsDragging = false; bAutoScroll = false; bIsAutoScrollFrameAligned = true; AutoScrollFrameType = TraceFrameType_Game; AutoScrollViewportOffsetPercent = 0.1; // scrolls forward 10% of viewport's width AutoScrollMinDelay = 0.3; // [seconds] LastAutoScrollTime = 0; bIsPanning = false; PanningMode = EPanningMode::None; OverscrollLeft = 0.0f; OverscrollRight = 0.0f; OverscrollTop = 0.0f; OverscrollBottom = 0.0f; bIsSelecting = false; SelectionStartTime = 0.0; SelectionEndTime = 0.0; if (HoveredTrack.IsValid()) { HoveredTrack.Reset(); OnHoveredTrackChangedDelegate.Broadcast(HoveredTrack); } if (HoveredEvent.IsValid()) { HoveredEvent.Reset(); OnHoveredEventChangedDelegate.Broadcast(HoveredEvent); } if (SelectedTrack.IsValid()) { SelectedTrack.Reset(); OnSelectedTrackChangedDelegate.Broadcast(SelectedTrack); } if (SelectedEvent.IsValid()) { SelectedEvent.Reset(); OnSelectedEventChangedDelegate.Broadcast(SelectedEvent); } if (TimingEventFilter.IsValid()) { TimingEventFilter.Reset(); } bPreventThrottling = false; Tooltip.Reset(); LastSelectionType = ESelectionType::None; // ThisGeometry bDrawTopSeparatorLine = false; bDrawBottomSeparatorLine = false; ////////////////////////////////////////////////// NumUpdatedEvents = 0; PreUpdateTracksDurationHistory.Reset(); PreUpdateTracksDurationHistory.AddValue(0); UpdateTracksDurationHistory.Reset(); UpdateTracksDurationHistory.AddValue(0); PostUpdateTracksDurationHistory.Reset(); PostUpdateTracksDurationHistory.AddValue(0); TickDurationHistory.Reset(); TickDurationHistory.AddValue(0); PreDrawTracksDurationHistory.Reset(); PreDrawTracksDurationHistory.AddValue(0); DrawTracksDurationHistory.Reset(); DrawTracksDurationHistory.AddValue(0); PostDrawTracksDurationHistory.Reset(); PostDrawTracksDurationHistory.AddValue(0); OnPaintDeltaTimeHistory.Reset(); OnPaintDeltaTimeHistory.AddValue(0); LastOnPaintTime = FPlatformTime::Cycles64(); ////////////////////////////////////////////////// for (Insights::ITimingViewExtender* Extender: GetExtenders()) { Extender->OnBeginSession(*this); } } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimingView::IsGpuTrackVisible() const { return ThreadTimingSharedState && ThreadTimingSharedState->IsGpuTrackVisible(); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimingView::IsCpuTrackVisible(uint32 InThreadId) const { return ThreadTimingSharedState && ThreadTimingSharedState->IsCpuTrackVisible(InThreadId); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) { // SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); FStopwatch TickStopwatch; TickStopwatch.Start(); ThisGeometry = AllottedGeometry; bPreventThrottling = false; constexpr float OverscrollFadeSpeed = 2.0f; if (OverscrollLeft > 0.0f) { OverscrollLeft = FMath::Max(0.0f, OverscrollLeft - InDeltaTime * OverscrollFadeSpeed); } if (OverscrollRight > 0.0f) { OverscrollRight = FMath::Max(0.0f, OverscrollRight - InDeltaTime * OverscrollFadeSpeed); } if (OverscrollTop > 0.0f) { OverscrollTop = FMath::Max(0.0f, OverscrollTop - InDeltaTime * OverscrollFadeSpeed); } if (OverscrollBottom > 0.0f) { OverscrollBottom = FMath::Max(0.0f, OverscrollBottom - InDeltaTime * OverscrollFadeSpeed); } const float ViewWidth = AllottedGeometry.GetLocalSize().X; const float ViewHeight = AllottedGeometry.GetLocalSize().Y; //////////////////////////////////////////////////////////////////////////////////////////////////// // Update viewport. Viewport.UpdateSize(ViewWidth, ViewHeight); if (!bIsPanning && !bAutoScroll) { // Elastic snap to horizontal time limits. if (Viewport.EnforceHorizontalScrollLimits(0.5)) // 0.5 is the interpolation factor { UpdateHorizontalScrollBar(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// // Check the analysis session time. TSharedPtr Session = FInsightsManager::Get()->GetSession(); if (Session) { double SessionTime = 0.0; { Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); SessionTime = Session->GetDurationSeconds(); } // Check if horizontal scroll area has changed. if (SessionTime > Viewport.GetMaxValidTime() && SessionTime != DBL_MAX && SessionTime != std::numeric_limits::infinity()) { const double PreviousSessionTime = Viewport.GetMaxValidTime(); if ((PreviousSessionTime >= Viewport.GetStartTime() && PreviousSessionTime <= Viewport.GetEndTime()) || (SessionTime >= Viewport.GetStartTime() && SessionTime <= Viewport.GetEndTime())) { Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HClippedSessionTimeChanged); } // UE_LOG(TimingProfiler, Log, TEXT("Session Duration: %g"), DT); Viewport.SetMaxValidTime(SessionTime); UpdateHorizontalScrollBar(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// if (bIsPanning) { // Disable auto-scroll if user starts panning manually. bAutoScroll = false; } if (bAutoScroll) { const uint64 CurrentTime = FPlatformTime::Cycles64(); if (static_cast(CurrentTime - LastAutoScrollTime) * FPlatformTime::GetSecondsPerCycle64() > AutoScrollMinDelay) { const double ViewportDuration = Viewport.GetEndTime() - Viewport.GetStartTime(); // width of the viewport in [seconds] const double AutoScrollViewportOffsetTime = ViewportDuration * AutoScrollViewportOffsetPercent; // By default, align the current session time with the offseted right side of the viewport. double MinStartTime = Viewport.GetMaxValidTime() - ViewportDuration + AutoScrollViewportOffsetTime; if (bIsAutoScrollFrameAligned) { if (Session.IsValid()) { Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); const Trace::IFrameProvider& FramesProvider = Trace::ReadFrameProvider(*Session.Get()); const uint64 FrameCount = FramesProvider.GetFrameCount(AutoScrollFrameType); if (FrameCount > 0) { // Search the last frame with EndTime <= SessionTime. uint64 FrameIndex = FrameCount; while (FrameIndex > 0) { const Trace::FFrame* FramePtr = FramesProvider.GetFrame(AutoScrollFrameType, --FrameIndex); if (FramePtr && FramePtr->EndTime <= Viewport.GetMaxValidTime()) { // Align the start time of the frame with the right side of the viewport. MinStartTime = FramePtr->EndTime - ViewportDuration + AutoScrollViewportOffsetTime; break; } } // Get the frame at the center of the viewport. Trace::FFrame Frame; const double ViewportCenter = MinStartTime + ViewportDuration / 2; if (FramesProvider.GetFrameFromTime(AutoScrollFrameType, ViewportCenter, Frame)) { if (Frame.EndTime > ViewportCenter) { // Align the start time of the frame with the center of the viewport. MinStartTime = Frame.StartTime - ViewportDuration / 2; } } } } } ScrollAtTime(MinStartTime); LastAutoScrollTime = CurrentTime; } } //////////////////////////////////////////////////////////////////////////////////////////////////// if (Session) { // Tick plugin extenders. // Each extender can add/remove tracks and/or change order of tracks. for (Insights::ITimingViewExtender* Extender: GetExtenders()) { Extender->Tick(*this, *Session.Get()); } // Re-sort now if we need to UpdateScrollableTracksOrder(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // Animate the (vertical) layout transition (i.e. compact mode <-> normal mode). Viewport.UpdateLayout(); TimeRulerTrack->SetSelection(bIsSelecting, SelectionStartTime, SelectionEndTime); //////////////////////////////////////////////////////////////////////////////////////////////////// class FTimingTrackUpdateContext: public ITimingTrackUpdateContext { public: explicit FTimingTrackUpdateContext(STimingView* InTimingView, double InCurrentTime, float InDeltaTime): TimingView(InTimingView), CurrentTime(InCurrentTime), DeltaTime(InDeltaTime) {} virtual const FTimingTrackViewport& GetViewport() const override { return TimingView->GetViewport(); } virtual const FVector2D& GetMousePosition() const override { return TimingView->GetMousePosition(); } virtual const TSharedPtr GetHoveredEvent() const override { return TimingView->GetHoveredEvent(); } virtual const TSharedPtr GetSelectedEvent() const override { return TimingView->GetSelectedEvent(); } virtual const TSharedPtr GetEventFilter() const override { return TimingView->GetEventFilter(); } virtual double GetCurrentTime() const override { return CurrentTime; } virtual float GetDeltaTime() const override { return DeltaTime; } public: STimingView* TimingView; double CurrentTime; float DeltaTime; }; FTimingTrackUpdateContext UpdateContext(this, InCurrentTime, InDeltaTime); //////////////////////////////////////////////////////////////////////////////////////////////////// // Pre-Update. // The tracks needs to update their size. { FStopwatch PreUpdateTracksStopwatch; PreUpdateTracksStopwatch.Start(); for (TSharedPtr& TrackPtr: TopDockedTracks) { if (TrackPtr->IsVisible()) { TrackPtr->PreUpdate(UpdateContext); } } for (TSharedPtr& TrackPtr: BottomDockedTracks) { if (TrackPtr->IsVisible()) { TrackPtr->PreUpdate(UpdateContext); } } for (TSharedPtr& TrackPtr: ScrollableTracks) { if (TrackPtr->IsVisible()) { TrackPtr->PreUpdate(UpdateContext); } } for (TSharedPtr& TrackPtr: ForegroundTracks) { if (TrackPtr->IsVisible()) { TrackPtr->PreUpdate(UpdateContext); } } PreUpdateTracksStopwatch.Stop(); PreUpdateTracksDurationHistory.AddValue(PreUpdateTracksStopwatch.AccumulatedTime); } //////////////////////////////////////////////////////////////////////////////////////////////////// // Update Y postion for the visible top docked tracks. // Compute the total height of top docked areas. int32 NumVisibleTopDockedTracks = 0; float TopOffset = 0.0f; for (TSharedPtr& TrackPtr: TopDockedTracks) { TrackPtr->SetPosY(TopOffset); if (TrackPtr->IsVisible()) { TopOffset += TrackPtr->GetHeight(); NumVisibleTopDockedTracks++; } } if (NumVisibleTopDockedTracks > 0) { bDrawTopSeparatorLine = true; TopOffset += 2.0f; } else { bDrawTopSeparatorLine = false; } Viewport.SetTopOffset(TopOffset); //////////////////////////////////////////////////////////////////////////////////////////////////// // Update Y postion for the visible bottom docked tracks. // Compute the total height of bottom docked areas. float BottomOffset = 0.0f; for (TSharedPtr& TrackPtr: BottomDockedTracks) { if (TrackPtr->IsVisible()) { BottomOffset += TrackPtr->GetHeight(); } } float BottomOffsetY = Viewport.GetHeight() - BottomOffset; int32 NumVisibleBottomDockedTracks = 0; for (TSharedPtr& TrackPtr: BottomDockedTracks) { TrackPtr->SetPosY(BottomOffsetY); if (TrackPtr->IsVisible()) { BottomOffsetY += TrackPtr->GetHeight(); NumVisibleBottomDockedTracks++; } } if (NumVisibleBottomDockedTracks > 0) { bDrawBottomSeparatorLine = true; BottomOffset += 2.0f; } else { bDrawBottomSeparatorLine = false; } Viewport.SetBottomOffset(BottomOffset); //////////////////////////////////////////////////////////////////////////////////////////////////// // Compute the total height of visible scrollable tracks. float ScrollHeight = 0.0f; for (TSharedPtr& TrackPtr: ScrollableTracks) { if (TrackPtr->IsVisible()) { ScrollHeight += TrackPtr->GetHeight(); } } ScrollHeight += 1.0f; // allow 1 pixel at the bottom (for last horizontal line) //////////////////////////////////////////////////////////////////////////////////////////////////// // Check if vertical scroll area has changed. bool bScrollHeightChanged = false; if (ScrollHeight != Viewport.GetScrollHeight()) { bScrollHeightChanged = true; Viewport.SetScrollHeight(ScrollHeight); UpdateVerticalScrollBar(); } // Set the VerticalScrollBar padding so it is limited to the scrollable area. VerticalScrollBar->SetPadding(FMargin(0.0f, TopOffset, 0.0f, FMath::Max(BottomOffset, 12.0f))); //////////////////////////////////////////////////////////////////////////////////////////////////// const float InitialScrollPosY = Viewport.GetScrollPosY(); TSharedPtr SelectedScrollableTrack; if (SelectedTrack.IsValid() && SelectedTrack->IsVisible()) { if (ScrollableTracks.Contains(SelectedTrack)) { SelectedScrollableTrack = SelectedTrack; } } const float InitialPinnedTrackPosY = SelectedScrollableTrack.IsValid() ? SelectedScrollableTrack->GetPosY() : 0.0f; // Update the Y position for visible scrollable tracks. UpdatePositionForScrollableTracks(); // The selected track will be pinned (keeps Y pos fixed unless user scrolls vertically). if (SelectedScrollableTrack.IsValid()) { const float ScrollingDY = LastScrollPosY - InitialScrollPosY; const float PinnedTrackPosY = SelectedScrollableTrack->GetPosY(); const float AdjustmentDY = InitialPinnedTrackPosY - PinnedTrackPosY + ScrollingDY; if (!FMath::IsNearlyZero(AdjustmentDY, 0.5f)) { ViewportScrollPosYOnButtonDown -= AdjustmentDY; ScrollAtPosY(InitialScrollPosY - AdjustmentDY); UpdatePositionForScrollableTracks(); } } // Elastic snap to vertical scroll limits. if (!bIsPanning) { const float DY = Viewport.GetScrollHeight() - Viewport.GetScrollableAreaHeight() + 7.0f; // +7 is to allow some space for the horizontal scrollbar const float MinY = FMath::Min(DY, 0.0f); const float MaxY = DY - MinY; float ScrollPosY = Viewport.GetScrollPosY(); if (ScrollPosY < MinY) { if (bScrollHeightChanged || Viewport.IsDirty(ETimingTrackViewportDirtyFlags::VLayoutChanged)) { ScrollPosY = MinY; } else { constexpr float U = 0.5f; ScrollPosY = ScrollPosY * U + (1.0f - U) * MinY; if (FMath::IsNearlyEqual(ScrollPosY, MinY, 0.5f)) { ScrollPosY = MinY; } } } else if (ScrollPosY > MaxY) { if (bScrollHeightChanged || Viewport.IsDirty(ETimingTrackViewportDirtyFlags::VLayoutChanged)) { ScrollPosY = MaxY; } else { constexpr float U = 0.5f; ScrollPosY = ScrollPosY * U + (1.0f - U) * MaxY; if (FMath::IsNearlyEqual(ScrollPosY, MaxY, 0.5f)) { ScrollPosY = MaxY; } } if (ScrollPosY < MinY) { ScrollPosY = MinY; } } if (ScrollPosY != Viewport.GetScrollPosY()) { ScrollAtPosY(ScrollPosY); UpdatePositionForScrollableTracks(); } } LastScrollPosY = Viewport.GetScrollPosY(); //////////////////////////////////////////////////////////////////////////////////////////////////// // At this point it is assumed all tracks have proper position and size. // Update. { FStopwatch UpdateTracksStopwatch; UpdateTracksStopwatch.Start(); for (TSharedPtr& TrackPtr: TopDockedTracks) { if (TrackPtr->IsVisible()) { TrackPtr->Update(UpdateContext); } } for (TSharedPtr& TrackPtr: BottomDockedTracks) { if (TrackPtr->IsVisible()) { TrackPtr->Update(UpdateContext); } } for (TSharedPtr& TrackPtr: ScrollableTracks) { if (TrackPtr->IsVisible()) { TrackPtr->Update(UpdateContext); } } for (TSharedPtr& TrackPtr: ForegroundTracks) { if (TrackPtr->IsVisible()) { TrackPtr->Update(UpdateContext); } } UpdateTracksStopwatch.Stop(); UpdateTracksDurationHistory.AddValue(UpdateTracksStopwatch.AccumulatedTime); } ////////////////////////////////////////////////// // Post-Update. { FStopwatch PostUpdateTracksStopwatch; PostUpdateTracksStopwatch.Start(); for (TSharedPtr& TrackPtr: TopDockedTracks) { if (TrackPtr->IsVisible()) { TrackPtr->PostUpdate(UpdateContext); } } for (TSharedPtr& TrackPtr: BottomDockedTracks) { if (TrackPtr->IsVisible()) { TrackPtr->PostUpdate(UpdateContext); } } for (TSharedPtr& TrackPtr: ScrollableTracks) { if (TrackPtr->IsVisible()) { TrackPtr->PostUpdate(UpdateContext); } } for (TSharedPtr& TrackPtr: ForegroundTracks) { if (TrackPtr->IsVisible()) { TrackPtr->PostUpdate(UpdateContext); } } PostUpdateTracksStopwatch.Stop(); PostUpdateTracksDurationHistory.AddValue(PostUpdateTracksStopwatch.AccumulatedTime); } ////////////////////////////////////////////////// Tooltip.Update(); if (!MousePosition.IsZero()) { Tooltip.SetPosition(MousePosition, 0.0f, Viewport.GetWidth() - 12.0f, 0.0f, Viewport.GetHeight() - 12.0f); // -12.0f is to avoid overlaping the scrollbars } ////////////////////////////////////////////////// // Reset hovered/selected flags for all tracks. for (TSharedPtr& TrackPtr: ScrollableTracks) { if (TrackPtr->IsVisible()) { TrackPtr->SetHoveredState(false); TrackPtr->SetSelectedFlag(false); } } // Set the hovered flag for the actual hovered track, if any. if (HoveredTrack.IsValid()) { HoveredTrack->SetHoveredState(true); } // Set the selected flag for the actual selected track, if any. if (SelectedTrack.IsValid()) { SelectedTrack->SetSelectedFlag(true); } ////////////////////////////////////////////////// Viewport.ResetDirtyFlags(); TickStopwatch.Stop(); TickDurationHistory.AddValue(TickStopwatch.AccumulatedTime); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::UpdatePositionForScrollableTracks() { // Update the Y postion for visible scrollable tracks. float ScrollableTrackPosY = Viewport.GetTopOffset() - Viewport.GetScrollPosY(); for (TSharedPtr& TrackPtr: ScrollableTracks) { TrackPtr->SetPosY(ScrollableTrackPosY); // set pos y also for the hidden tracks if (TrackPtr->IsVisible()) { ScrollableTrackPosY += TrackPtr->GetHeight(); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// int32 STimingView::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { FStopwatch Stopwatch; Stopwatch.Start(); const bool bEnabled = ShouldBeEnabled(bParentEnabled); const ESlateDrawEffect DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect; FDrawContext DrawContext(AllottedGeometry, MyCullingRect, InWidgetStyle, DrawEffects, OutDrawElements, LayerId); #if 0 // Enabling this may further increase UI performance (TODO: profile if this is really needed again). // Avoids multiple resizes of Slate's draw elements buffers. OutDrawElements.GetRootDrawLayer().DrawElements.Reserve(50000); #endif const TSharedRef FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService(); const float ViewWidth = AllottedGeometry.GetLocalSize().X; const float ViewHeight = AllottedGeometry.GetLocalSize().Y; #if 0 // Enabling this may further increase UI performance (TODO: profile if this is really needed again). // Warm up Slate vertex/index buffers to avoid initial freezes due to multiple resizes of those buffers. static bool bWarmingUp = false; if (!bWarmingUp) { bWarmingUp = true; FRandomStream RandomStream(0); const int32 Count = 1'000'000; for (int32 Index = 0; Index < Count; ++Index) { float X = ViewWidth * RandomStream.GetFraction(); float Y = ViewHeight * RandomStream.GetFraction(); FLinearColor Color(RandomStream.GetFraction(), RandomStream.GetFraction(), RandomStream.GetFraction(), 1.0f); DrawContext.DrawBox(X, Y, 1.0f, 1.0f, WhiteBrush, Color); } LayerId++; LayerId++; } #endif //////////////////////////////////////////////////////////////////////////////////////////////////// class FTimingTrackDrawContext: public ITimingTrackDrawContext { public: explicit FTimingTrackDrawContext(const STimingView* InTimingView, FDrawContext& InDrawContext, const FTimingViewDrawHelper& InHelper) : TimingView(InTimingView), DrawContext(InDrawContext), Helper(InHelper) {} virtual const FTimingTrackViewport& GetViewport() const override { return TimingView->GetViewport(); } virtual const FVector2D& GetMousePosition() const override { return TimingView->GetMousePosition(); } virtual const TSharedPtr GetHoveredEvent() const override { return TimingView->GetHoveredEvent(); } virtual const TSharedPtr GetSelectedEvent() const override { return TimingView->GetSelectedEvent(); } virtual const TSharedPtr GetEventFilter() const override { return TimingView->GetEventFilter(); } virtual FDrawContext& GetDrawContext() const override { return DrawContext; } virtual const ITimingViewDrawHelper& GetHelper() const override { return Helper; } public: const STimingView* TimingView; FDrawContext& DrawContext; const FTimingViewDrawHelper& Helper; }; FTimingViewDrawHelper Helper(DrawContext, Viewport); FTimingTrackDrawContext TimingDrawContext(this, DrawContext, Helper); //////////////////////////////////////////////////////////////////////////////////////////////////// // Draw background. Helper.DrawBackground(); ////////////////////////////////////////////////// // Pre-Draw { FStopwatch PreDrawTracksStopwatch; PreDrawTracksStopwatch.Start(); for (const TSharedPtr& TrackPtr: ScrollableTracks) { if (TrackPtr->IsVisible()) { TrackPtr->PreDraw(TimingDrawContext); } } for (const TSharedPtr& TrackPtr: TopDockedTracks) { if (TrackPtr->IsVisible()) { TrackPtr->PreDraw(TimingDrawContext); } } for (const TSharedPtr& TrackPtr: BottomDockedTracks) { if (TrackPtr->IsVisible()) { TrackPtr->PreDraw(TimingDrawContext); } } for (const TSharedPtr& TrackPtr: ForegroundTracks) { if (TrackPtr->IsVisible()) { TrackPtr->PreDraw(TimingDrawContext); } } PreDrawTracksStopwatch.Stop(); PreDrawTracksDurationHistory.AddValue(PreDrawTracksStopwatch.AccumulatedTime); } ////////////////////////////////////////////////// // Draw { FStopwatch DrawTracksStopwatch; DrawTracksStopwatch.Start(); Helper.BeginDrawTracks(); const FVector2D Position = AllottedGeometry.GetAbsolutePosition(); const float Scale = AllottedGeometry.GetAccumulatedLayoutTransform().GetScale(); // Draw the scrollable tracks. { const float TopY = Viewport.GetTopOffset(); const float BottomY = Viewport.GetHeight() - Viewport.GetBottomOffset(); const float L = Position.X; const float R = Position.X + (Viewport.GetWidth() * Scale); const float T = Position.Y + (TopY * Scale); const float B = Position.Y + (BottomY * Scale); const FSlateClippingZone ClipZone(FVector2D(L, T), FVector2D(R, T), FVector2D(L, B), FVector2D(R, B)); DrawContext.ElementList.PushClip(ClipZone); for (const TSharedPtr& TrackPtr: ScrollableTracks) { if (TrackPtr->IsVisible()) { if (TrackPtr->GetPosY() + TrackPtr->GetHeight() <= TopY) { continue; } if (TrackPtr->GetPosY() >= BottomY) { break; } TrackPtr->Draw(TimingDrawContext); } } DrawContext.ElementList.PopClip(); } // Draw the top docked tracks. { const float L = Position.X; const float R = Position.X + (Viewport.GetWidth() * Scale); const float T = Position.Y; const float B = Position.Y + (Viewport.GetTopOffset() * Scale); const FSlateClippingZone ClipZone(FVector2D(L, T), FVector2D(R, T), FVector2D(L, B), FVector2D(R, B)); DrawContext.ElementList.PushClip(ClipZone); for (const TSharedPtr& TrackPtr: TopDockedTracks) { if (TrackPtr->IsVisible()) { TrackPtr->Draw(TimingDrawContext); } } if (bDrawTopSeparatorLine) { // Draw separator line between top docked tracks and scrollable tracks. DrawContext.DrawBox(0.0f, Viewport.GetTopOffset() - 2.0f, Viewport.GetWidth(), 2.0f, WhiteBrush, FLinearColor(0.01f, 0.01f, 0.01f, 1.0f)); ++DrawContext.LayerId; } DrawContext.ElementList.PopClip(); } // Draw the bottom docked tracks. { const float L = Position.X; const float R = Position.X + (Viewport.GetWidth() * Scale); const float T = Position.Y + ((Viewport.GetHeight() - Viewport.GetBottomOffset()) * Scale); const float B = Position.Y + (Viewport.GetHeight() * Scale); const FSlateClippingZone ClipZone(FVector2D(L, T), FVector2D(R, T), FVector2D(L, B), FVector2D(R, B)); DrawContext.ElementList.PushClip(ClipZone); for (const TSharedPtr& TrackPtr: BottomDockedTracks) { if (TrackPtr->IsVisible()) { TrackPtr->Draw(TimingDrawContext); } } if (bDrawBottomSeparatorLine) { // Draw separator line between top docked tracks and scrollable tracks. DrawContext.DrawBox(0.0f, Viewport.GetHeight() - Viewport.GetBottomOffset(), Viewport.GetWidth(), 2.0f, WhiteBrush, FLinearColor(0.01f, 0.01f, 0.01f, 1.0f)); ++DrawContext.LayerId; } DrawContext.ElementList.PopClip(); } // Draw the foreground tracks. for (const TSharedPtr& TrackPtr: ForegroundTracks) { if (TrackPtr->IsVisible()) { TrackPtr->Draw(TimingDrawContext); } } Helper.EndDrawTracks(); DrawTracksStopwatch.Stop(); DrawTracksDurationHistory.AddValue(DrawTracksStopwatch.AccumulatedTime); } ////////////////////////////////////////////////// // Draw the selected and/or hovered event. if (ITimingEvent::AreValidAndEquals(SelectedEvent, HoveredEvent)) { const TSharedRef TrackPtr = SelectedEvent->GetTrack(); // Highlight the selected and hovered timing event (if any). if (TrackPtr->IsVisible()) { SelectedEvent->GetTrack()->DrawEvent(TimingDrawContext, *SelectedEvent, EDrawEventMode::SelectedAndHovered); } } else { // Highlight the selected timing event (if any). if (SelectedEvent.IsValid()) { const TSharedRef TrackPtr = SelectedEvent->GetTrack(); if (TrackPtr->IsVisible()) { SelectedEvent->GetTrack()->DrawEvent(TimingDrawContext, *SelectedEvent, EDrawEventMode::Selected); } } // Highlight the hovered timing event (if any). if (HoveredEvent.IsValid()) { const TSharedRef TrackPtr = HoveredEvent->GetTrack(); if (TrackPtr->IsVisible()) { HoveredEvent->GetTrack()->DrawEvent(TimingDrawContext, *HoveredEvent, EDrawEventMode::Hovered); } } } // Draw the time range selection. FDrawHelpers::DrawTimeRangeSelection(DrawContext, Viewport, SelectionStartTime, SelectionEndTime, WhiteBrush, MainFont); ////////////////////////////////////////////////// // Post-Draw { FStopwatch PostDrawTracksStopwatch; PostDrawTracksStopwatch.Start(); for (const TSharedPtr& TrackPtr: ScrollableTracks) { if (TrackPtr->IsVisible()) { TrackPtr->PostDraw(TimingDrawContext); } } for (const TSharedPtr& TrackPtr: TopDockedTracks) { if (TrackPtr->IsVisible()) { TrackPtr->PostDraw(TimingDrawContext); } } for (const TSharedPtr& TrackPtr: BottomDockedTracks) { if (TrackPtr->IsVisible()) { TrackPtr->PostDraw(TimingDrawContext); } } for (const TSharedPtr& TrackPtr: ForegroundTracks) { if (TrackPtr->IsVisible()) { TrackPtr->PostDraw(TimingDrawContext); } } PostDrawTracksStopwatch.Stop(); PostDrawTracksDurationHistory.AddValue(PostDrawTracksStopwatch.AccumulatedTime); } ////////////////////////////////////////////////// // Draw tooltip with info about hovered event. Tooltip.Draw(DrawContext); // Fill background for the "Tracks" filter combobox. DrawContext.DrawBox(0.0f, 0.0f, 66.0f, 24.0f, WhiteBrush, FLinearColor(0.05f, 0.05f, 0.05f, 1.0f)); // Fill background for the "Auto-scroll" toggle button. DrawContext.DrawBox(ViewWidth - 40.0f, 0.0f, 40.0f, 24.0f, WhiteBrush, FLinearColor(0.05f, 0.05f, 0.05f, 1.0f)); ////////////////////////////////////////////////// // Draw the overscroll indication lines. constexpr float OverscrollLineSize = 1.0f; constexpr int32 OverscrollLineCount = 8; if (OverscrollLeft > 0.0f) { // TODO: single box with gradient opacity const float OverscrollLineY = Viewport.GetTopOffset(); const float OverscrollLineH = Viewport.GetScrollableAreaHeight(); for (int32 LineIndex = 0; LineIndex < OverscrollLineCount; ++LineIndex) { const float Opacity = OverscrollLeft * static_cast(OverscrollLineCount - LineIndex) / static_cast(OverscrollLineCount); DrawContext.DrawBox(LineIndex * OverscrollLineSize, OverscrollLineY, OverscrollLineSize, OverscrollLineH, WhiteBrush, FLinearColor(1.0f, 0.1f, 0.1f, Opacity)); } } if (OverscrollRight > 0.0f) { const float OverscrollLineY = Viewport.GetTopOffset(); const float OverscrollLineH = Viewport.GetScrollableAreaHeight(); for (int32 LineIndex = 0; LineIndex < OverscrollLineCount; ++LineIndex) { const float Opacity = OverscrollRight * static_cast(OverscrollLineCount - LineIndex) / static_cast(OverscrollLineCount); DrawContext.DrawBox(ViewWidth - (1 + LineIndex) * OverscrollLineSize, OverscrollLineY, OverscrollLineSize, OverscrollLineH, WhiteBrush, FLinearColor(1.0f, 0.1f, 0.1f, Opacity)); } } if (OverscrollTop > 0.0f) { const float OverscrollLineY = Viewport.GetTopOffset(); for (int32 LineIndex = 0; LineIndex < OverscrollLineCount; ++LineIndex) { const float Opacity = OverscrollTop * static_cast(OverscrollLineCount - LineIndex) / static_cast(OverscrollLineCount); DrawContext.DrawBox(0.0f, OverscrollLineY + LineIndex * OverscrollLineSize, ViewWidth, OverscrollLineSize, WhiteBrush, FLinearColor(1.0f, 0.1f, 0.1f, Opacity)); } } if (OverscrollBottom > 0.0f) { const float OverscrollLineY = ViewHeight - Viewport.GetBottomOffset(); for (int32 LineIndex = 0; LineIndex < OverscrollLineCount; ++LineIndex) { const float Opacity = OverscrollBottom * static_cast(OverscrollLineCount - LineIndex) / static_cast(OverscrollLineCount); DrawContext.DrawBox(0.0f, OverscrollLineY - (1 + LineIndex) * OverscrollLineSize, ViewWidth, OverscrollLineSize, WhiteBrush, FLinearColor(1.0f, 0.1f, 0.1f, Opacity)); } } ////////////////////////////////////////////////// const bool bShouldDisplayDebugInfo = FInsightsManager::Get()->IsDebugInfoEnabled(); if (bShouldDisplayDebugInfo) { const FSlateFontInfo& SummaryFont = MainFont; const float MaxFontCharHeight = FontMeasureService->Measure(TEXT("!"), SummaryFont).Y; const float DbgDY = MaxFontCharHeight; const float DbgW = 320.0f; const float DbgH = DbgDY * 9 + 3.0f; const float DbgX = ViewWidth - DbgW - 20.0f; float DbgY = Viewport.GetTopOffset() + 10.0f; DrawContext.LayerId++; DrawContext.DrawBox(DbgX - 2.0f, DbgY - 2.0f, DbgW, DbgH, WhiteBrush, FLinearColor(1.0f, 1.0f, 1.0f, 0.9f)); DrawContext.LayerId++; FLinearColor DbgTextColor(0.0f, 0.0f, 0.0f, 0.9f); ////////////////////////////////////////////////// // Display the "Draw" performance info. // Time interval since last OnPaint call. const uint64 CurrentTime = FPlatformTime::Cycles64(); const uint64 OnPaintDeltaTime = CurrentTime - LastOnPaintTime; LastOnPaintTime = CurrentTime; OnPaintDeltaTimeHistory.AddValue(OnPaintDeltaTime); // saved for last 32 OnPaint calls const uint64 AvgOnPaintDeltaTime = OnPaintDeltaTimeHistory.ComputeAverage(); const uint64 AvgOnPaintDeltaTimeMs = FStopwatch::Cycles64ToMilliseconds(AvgOnPaintDeltaTime); const double AvgOnPaintFps = AvgOnPaintDeltaTimeMs != 0 ? 1.0 / FStopwatch::Cycles64ToSeconds(AvgOnPaintDeltaTime) : 0.0; const uint64 AvgPreDrawTracksDurationMs = FStopwatch::Cycles64ToMilliseconds(PreDrawTracksDurationHistory.ComputeAverage()); const uint64 AvgDrawTracksDurationMs = FStopwatch::Cycles64ToMilliseconds(DrawTracksDurationHistory.ComputeAverage()); const uint64 AvgPostDrawTracksDurationMs = FStopwatch::Cycles64ToMilliseconds(PostDrawTracksDurationHistory.ComputeAverage()); const uint64 AvgTotalDrawDurationMs = FStopwatch::Cycles64ToMilliseconds(TotalDrawDurationHistory.ComputeAverage()); DrawContext.DrawText( DbgX, DbgY, FString::Printf(TEXT("D: %llu ms + %llu ms + %llu ms + %llu ms = %llu ms | + %llu ms = %llu ms (%d fps)"), AvgPreDrawTracksDurationMs, // pre-draw tracks time AvgDrawTracksDurationMs, // draw tracks time AvgPostDrawTracksDurationMs, // post-draw tracks time AvgTotalDrawDurationMs - AvgPreDrawTracksDurationMs - AvgDrawTracksDurationMs - AvgPostDrawTracksDurationMs, // other draw code AvgTotalDrawDurationMs, AvgOnPaintDeltaTimeMs - AvgTotalDrawDurationMs, // other overhead to OnPaint calls AvgOnPaintDeltaTimeMs, // average time between two OnPaint calls FMath::RoundToInt(AvgOnPaintFps)), // framerate of OnPaint calls SummaryFont, DbgTextColor); DbgY += DbgDY; ////////////////////////////////////////////////// // Display the "update" performance info. const uint64 AvgPreUpdateTracksDurationMs = FStopwatch::Cycles64ToMilliseconds(PreUpdateTracksDurationHistory.ComputeAverage()); const uint64 AvgUpdateTracksDurationMs = FStopwatch::Cycles64ToMilliseconds(UpdateTracksDurationHistory.ComputeAverage()); const uint64 AvgPostUpdateTracksDurationMs = FStopwatch::Cycles64ToMilliseconds(PostUpdateTracksDurationHistory.ComputeAverage()); const uint64 AvgTickDurationMs = FStopwatch::Cycles64ToMilliseconds(TickDurationHistory.ComputeAverage()); DrawContext.DrawText( DbgX, DbgY, FString::Printf(TEXT("U avg: %llu ms + %llu ms + %llu ms + %llu ms = %llu ms"), AvgPreUpdateTracksDurationMs, AvgUpdateTracksDurationMs, AvgPostUpdateTracksDurationMs, AvgTickDurationMs - AvgPreUpdateTracksDurationMs - AvgUpdateTracksDurationMs - AvgPostUpdateTracksDurationMs, AvgTickDurationMs), SummaryFont, DbgTextColor); DbgY += DbgDY; ////////////////////////////////////////////////// // Display timing events stats. DrawContext.DrawText( DbgX, DbgY, FString::Format(TEXT("{0} events : {1} ({2}) boxes, {3} borders, {4} texts"), { FText::AsNumber(Helper.GetNumEvents()).ToString(), FText::AsNumber(Helper.GetNumDrawBoxes()).ToString(), FText::AsPercent((double)Helper.GetNumDrawBoxes() / (Helper.GetNumDrawBoxes() + Helper.GetNumMergedBoxes())).ToString(), FText::AsNumber(Helper.GetNumDrawBorders()).ToString(), FText::AsNumber(Helper.GetNumDrawTexts()).ToString(), // OutDrawElements.GetRootDrawLayer().GetElementCount(), }), SummaryFont, DbgTextColor); DbgY += DbgDY; ////////////////////////////////////////////////// // Display time markers stats. if (MarkersTrack->IsVisible()) { DrawContext.DrawText( DbgX, DbgY, FString::Format(TEXT("{0} logs : {1} boxes, {2} texts"), { FText::AsNumber(MarkersTrack->GetNumLogMessages()).ToString(), FText::AsNumber(MarkersTrack->GetNumBoxes()).ToString(), FText::AsNumber(MarkersTrack->GetNumTexts()).ToString(), }), SummaryFont, DbgTextColor); DbgY += DbgDY; } ////////////////////////////////////////////////// // Display Graph track stats. if (GraphTrack) { DrawContext.DrawText( DbgX, DbgY, FString::Format(TEXT("{0} events : {1} points, {2} lines, {3} boxes"), { FText::AsNumber(GraphTrack->GetNumAddedEvents()).ToString(), FText::AsNumber(GraphTrack->GetNumDrawPoints()).ToString(), FText::AsNumber(GraphTrack->GetNumDrawLines()).ToString(), FText::AsNumber(GraphTrack->GetNumDrawBoxes()).ToString(), }), SummaryFont, DbgTextColor); DbgY += DbgDY; } ////////////////////////////////////////////////// // Display viewport's horizontal info. DrawContext.DrawText( DbgX, DbgY, FString::Printf(TEXT("SX: %g, ST: %g, ET: %s"), Viewport.GetScaleX(), Viewport.GetStartTime(), *TimeUtils::FormatTimeAuto(Viewport.GetMaxValidTime())), SummaryFont, DbgTextColor); DbgY += DbgDY; ////////////////////////////////////////////////// // Display viewport's vertical info. DrawContext.DrawText( DbgX, DbgY, FString::Printf(TEXT("Y: %.2f, H: %g, VH: %g"), Viewport.GetScrollPosY(), Viewport.GetScrollHeight(), Viewport.GetHeight()), SummaryFont, DbgTextColor); DbgY += DbgDY; ////////////////////////////////////////////////// // Display input related debug info. FString InputStr = FString::Printf(TEXT("(%.0f, %.0f)"), MousePosition.X, MousePosition.Y); if (bIsSpaceBarKeyPressed) InputStr += " Space"; if (bIsLMB_Pressed) InputStr += " LMB"; if (bIsRMB_Pressed) InputStr += " RMB"; if (bIsPanning) InputStr += " Panning"; if (bIsSelecting) InputStr += " Selecting"; if (bIsDragging) InputStr += " Dragging"; if (TimeRulerTrack->IsScrubbing()) InputStr += " Scrubbing"; DrawContext.DrawText(DbgX, DbgY, InputStr, SummaryFont, DbgTextColor); DbgY += DbgDY; } ////////////////////////////////////////////////// Stopwatch.Stop(); TotalDrawDurationHistory.AddValue(Stopwatch.AccumulatedTime); return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled && IsEnabled()); } //////////////////////////////////////////////////////////////////////////////////////////////////// const TCHAR* STimingView::GetLocationName(ETimingTrackLocation Location) { switch (Location) { case ETimingTrackLocation::TopDocked: return TEXT("Top Docked"); case ETimingTrackLocation::BottomDocked: return TEXT("Bottom Docked"); case ETimingTrackLocation::Scrollable: return TEXT("Scrollable"); case ETimingTrackLocation::Foreground: return TEXT("Foreground"); default: return nullptr; } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::AddTrack(TSharedPtr Track, ETimingTrackLocation Location) { check(Track.IsValid()); check(Location == ETimingTrackLocation::Scrollable || Location == ETimingTrackLocation::TopDocked || Location == ETimingTrackLocation::BottomDocked || Location == ETimingTrackLocation::Foreground); const TCHAR* LocationName = GetLocationName(Location); TArray>& TrackList = const_cast>&>(GetTrackList(Location)); const int32 MaxNumTracks = 1000; if (TrackList.Num() >= MaxNumTracks) { UE_LOG(TimingProfiler, Warning, TEXT("Too many tracks already created (%d tracks)! Ignoring %s track : %s (\"%s\")"), TrackList.Num(), LocationName, *Track->GetTypeName().ToString(), *Track->GetName()); return; } UE_LOG(TimingProfiler, Log, TEXT("New %s Track (%d) : %s (\"%s\")"), LocationName, TrackList.Num() + 1, *Track->GetTypeName().ToString(), *Track->GetName()); ensure(Track->GetLocation() == ETimingTrackLocation::None); Track->SetLocation(Location); check(!AllTracks.Contains(Track->GetId())); AllTracks.Add(Track->GetId(), Track); TrackList.Add(Track); Algo::SortBy(TrackList, &FBaseTimingTrack::GetOrder); if (Location == ETimingTrackLocation::Scrollable) { InvalidateScrollableTracksOrder(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimingView::RemoveTrack(TSharedPtr Track) { check(Track.IsValid()); if (AllTracks.Remove(Track->GetId()) > 0) { const ETimingTrackLocation Location = Track->GetLocation(); check(Location == ETimingTrackLocation::Scrollable || Location == ETimingTrackLocation::TopDocked || Location == ETimingTrackLocation::BottomDocked || Location == ETimingTrackLocation::Foreground); Track->SetLocation(ETimingTrackLocation::None); const TCHAR* LocationName = GetLocationName(Location); TArray>& TrackList = const_cast>&>(GetTrackList(Location)); TrackList.Remove(Track); if (Location == ETimingTrackLocation::Scrollable) { InvalidateScrollableTracksOrder(); } UE_LOG(TimingProfiler, Log, TEXT("Removed %s Track (%d) : %s (\"%s\")"), LocationName, TrackList.Num(), *Track->GetTypeName().ToString(), *Track->GetName()); return true; } return false; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::HideAllScrollableTracks() { for (TSharedPtr& Track: ScrollableTracks) { Track->Hide(); } OnTrackVisibilityChanged(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::InvalidateScrollableTracksOrder() { bScrollableTracksOrderIsDirty = true; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::UpdateScrollableTracksOrder() { if (bScrollableTracksOrderIsDirty) { Algo::SortBy(ScrollableTracks, &FBaseTimingTrack::GetOrder); bScrollableTracksOrderIsDirty = false; } } //////////////////////////////////////////////////////////////////////////////////////////////////// int32 STimingView::GetFirstScrollableTrackOrder() const { return (ScrollableTracks.Num() > 0) ? ScrollableTracks[0]->GetOrder() : 1; } //////////////////////////////////////////////////////////////////////////////////////////////////// int32 STimingView::GetLastScrollableTrackOrder() const { return (ScrollableTracks.Num() > 0) ? ScrollableTracks.Last()->GetOrder() : -1; } //////////////////////////////////////////////////////////////////////////////////////////////////// FReply STimingView::AllowTracksToProcessOnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { for (TSharedPtr& TrackPtr: TopDockedTracks) { if (TrackPtr->IsVisible()) { FReply Reply = TrackPtr->OnMouseButtonDown(MyGeometry, MouseEvent); if (Reply.IsEventHandled()) { return Reply; } } } for (TSharedPtr& TrackPtr: BottomDockedTracks) { if (TrackPtr->IsVisible()) { FReply Reply = TrackPtr->OnMouseButtonDown(MyGeometry, MouseEvent); if (Reply.IsEventHandled()) { return Reply; } } } for (TSharedPtr& TrackPtr: ScrollableTracks) { if (TrackPtr->IsVisible()) { FReply Reply = TrackPtr->OnMouseButtonDown(MyGeometry, MouseEvent); if (Reply.IsEventHandled()) { return Reply; } } } for (TSharedPtr& TrackPtr: ForegroundTracks) { if (TrackPtr->IsVisible()) { FReply Reply = TrackPtr->OnMouseButtonDown(MyGeometry, MouseEvent); if (Reply.IsEventHandled()) { return Reply; } } } return FReply::Unhandled(); } //////////////////////////////////////////////////////////////////////////////////////////////////// FReply STimingView::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { FReply Reply = AllowTracksToProcessOnMouseButtonDown(MyGeometry, MouseEvent); if (Reply.IsEventHandled()) { return Reply; } MousePositionOnButtonDown = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()); MousePosition = MousePositionOnButtonDown; bool bStartPanningSelectingOrScrubbing = false; bool bStartPanning = false; bool bStartSelecting = false; bool bStartScrubbing = false; if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) { if (!bIsRMB_Pressed) { bIsLMB_Pressed = true; bStartPanningSelectingOrScrubbing = true; SelectHoveredTimingTrack(); } } else if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton) { if (!bIsLMB_Pressed) { bIsRMB_Pressed = true; bStartPanningSelectingOrScrubbing = true; SelectHoveredTimingTrack(); } } TSharedPtr ScrubbingTimeMarker = nullptr; if (bStartPanningSelectingOrScrubbing) { bool bIsHoveringTimeRulerTrack = false; if (TimeRulerTrack->IsVisible()) { bIsHoveringTimeRulerTrack = MousePositionOnButtonDown.Y >= TimeRulerTrack->GetPosY() && MousePositionOnButtonDown.Y < TimeRulerTrack->GetPosY() + TimeRulerTrack->GetHeight(); if (bIsHoveringTimeRulerTrack) { if (MouseEvent.GetModifierKeys().IsControlDown()) { ScrubbingTimeMarker = DefaultTimeMarker; } else { ScrubbingTimeMarker = TimeRulerTrack->GetTimeMarkerAtPos(MousePositionOnButtonDown, Viewport); } } } if (bIsSpaceBarKeyPressed) { bStartPanning = true; } else if (ScrubbingTimeMarker) { bStartScrubbing = true; } else if (bIsHoveringTimeRulerTrack || (MouseEvent.GetModifierKeys().IsControlDown() && MouseEvent.GetModifierKeys().IsShiftDown())) { bStartSelecting = true; } else { bStartPanning = true; } // Capture mouse, so we can drag outside this widget. Reply = FReply::Handled().CaptureMouse(SharedThis(this)); } if (bPreventThrottling) { Reply.PreventThrottling(); } if (bStartScrubbing) { bIsPanning = false; bIsDragging = false; TimeRulerTrack->StartScrubbing(ScrubbingTimeMarker.ToSharedRef()); } else if (bStartPanning) { bIsPanning = true; bIsDragging = false; TimeRulerTrack->StopScrubbing(); ViewportStartTimeOnButtonDown = Viewport.GetStartTime(); ViewportScrollPosYOnButtonDown = Viewport.GetScrollPosY(); if (MouseEvent.GetModifierKeys().IsControlDown()) { // Allow panning only horizontally. PanningMode = EPanningMode::Horizontal; } else if (MouseEvent.GetModifierKeys().IsShiftDown()) { // Allow panning only vertically. PanningMode = EPanningMode::Vertical; } else { // Allow panning both horizontally and vertically. PanningMode = EPanningMode::HorizontalAndVertical; } } else if (bStartSelecting) { bIsSelecting = true; bIsDragging = false; TimeRulerTrack->StopScrubbing(); SelectionStartTime = Viewport.SlateUnitsToTime(MousePositionOnButtonDown.X); SelectionEndTime = SelectionStartTime; LastSelectionType = ESelectionType::None; RaiseSelectionChanging(); } return Reply; } //////////////////////////////////////////////////////////////////////////////////////////////////// FReply STimingView::AllowTracksToProcessOnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { for (TSharedPtr& TrackPtr: TopDockedTracks) { if (TrackPtr->IsVisible()) { FReply Reply = TrackPtr->OnMouseButtonUp(MyGeometry, MouseEvent); if (Reply.IsEventHandled()) { return Reply; } } } for (TSharedPtr& TrackPtr: BottomDockedTracks) { if (TrackPtr->IsVisible()) { FReply Reply = TrackPtr->OnMouseButtonUp(MyGeometry, MouseEvent); if (Reply.IsEventHandled()) { return Reply; } } } for (TSharedPtr& TrackPtr: ScrollableTracks) { if (TrackPtr->IsVisible()) { FReply Reply = TrackPtr->OnMouseButtonUp(MyGeometry, MouseEvent); if (Reply.IsEventHandled()) { return Reply; } } } for (TSharedPtr& TrackPtr: ForegroundTracks) { if (TrackPtr->IsVisible()) { FReply Reply = TrackPtr->OnMouseButtonUp(MyGeometry, MouseEvent); if (Reply.IsEventHandled()) { return Reply; } } } return FReply::Unhandled(); } //////////////////////////////////////////////////////////////////////////////////////////////////// FReply STimingView::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { FReply Reply = AllowTracksToProcessOnMouseButtonUp(MyGeometry, MouseEvent); if (Reply.IsEventHandled()) { return Reply; } MousePositionOnButtonUp = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()); MousePosition = MousePositionOnButtonUp; const bool bIsValidForMouseClick = MousePositionOnButtonUp.Equals(MousePositionOnButtonDown, 2.0f); if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) { if (bIsLMB_Pressed) { if (bIsPanning) { PanningMode = EPanningMode::None; bIsPanning = false; } else if (bIsSelecting) { RaiseSelectionChanged(); bIsSelecting = false; } else if (TimeRulerTrack->IsScrubbing()) { RaiseTimeMarkerChanged(); TimeRulerTrack->StopScrubbing(); } if (bIsValidForMouseClick) { // Select the hovered timing event (if any). UpdateHoveredTimingEvent(MousePositionOnButtonUp.X, MousePositionOnButtonUp.Y); SelectHoveredTimingTrack(); SelectHoveredTimingEvent(); // When clicking on an empty space... if (!SelectedEvent.IsValid()) { // ...reset selection. SelectionEndTime = SelectionStartTime = 0.0; LastSelectionType = ESelectionType::None; RaiseSelectionChanged(); } } bIsDragging = false; // Release mouse as we no longer drag. Reply = FReply::Handled().ReleaseMouseCapture(); bIsLMB_Pressed = false; } } else if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton) { if (bIsRMB_Pressed) { if (bIsPanning) { PanningMode = EPanningMode::None; bIsPanning = false; } else if (bIsSelecting) { RaiseSelectionChanged(); bIsSelecting = false; } else if (TimeRulerTrack->IsScrubbing()) { RaiseTimeMarkerChanged(); TimeRulerTrack->StopScrubbing(); } if (bIsValidForMouseClick) { SelectHoveredTimingTrack(); ShowContextMenu(MouseEvent); } bIsDragging = false; // Release mouse as we no longer drag. Reply = FReply::Handled().ReleaseMouseCapture(); bIsRMB_Pressed = false; } } return Reply; } //////////////////////////////////////////////////////////////////////////////////////////////////// FReply STimingView::AllowTracksToProcessOnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { for (TSharedPtr& TrackPtr: TopDockedTracks) { if (TrackPtr->IsVisible()) { FReply Reply = TrackPtr->OnMouseButtonDoubleClick(MyGeometry, MouseEvent); if (Reply.IsEventHandled()) { return Reply; } } } for (TSharedPtr& TrackPtr: BottomDockedTracks) { if (TrackPtr->IsVisible()) { FReply Reply = TrackPtr->OnMouseButtonDoubleClick(MyGeometry, MouseEvent); if (Reply.IsEventHandled()) { return Reply; } } } for (TSharedPtr& TrackPtr: ScrollableTracks) { if (TrackPtr->IsVisible()) { FReply Reply = TrackPtr->OnMouseButtonDoubleClick(MyGeometry, MouseEvent); if (Reply.IsEventHandled()) { return Reply; } } } for (TSharedPtr& TrackPtr: ForegroundTracks) { if (TrackPtr->IsVisible()) { FReply Reply = TrackPtr->OnMouseButtonDoubleClick(MyGeometry, MouseEvent); if (Reply.IsEventHandled()) { return Reply; } } } return FReply::Unhandled(); } //////////////////////////////////////////////////////////////////////////////////////////////////// FReply STimingView::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { FReply Reply = AllowTracksToProcessOnMouseButtonDoubleClick(MyGeometry, MouseEvent); if (Reply.IsEventHandled()) { return Reply; } if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) { if (HoveredEvent.IsValid()) { if (MouseEvent.GetModifierKeys().IsControlDown()) { const double EndTime = Viewport.RestrictEndTime(HoveredEvent->GetEndTime()); SelectTimeInterval(HoveredEvent->GetStartTime(), EndTime - HoveredEvent->GetStartTime()); } else { SetEventFilter(HoveredEvent->GetTrack()->GetFilterByEvent(HoveredEvent)); } } else { if (TimingEventFilter.IsValid()) { TimingEventFilter.Reset(); Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated); } } Reply = FReply::Handled(); } return Reply; } //////////////////////////////////////////////////////////////////////////////////////////////////// FReply STimingView::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { FReply Reply = FReply::Unhandled(); MousePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()); if (!MouseEvent.GetCursorDelta().IsZero()) { if (bIsPanning) { if (HasMouseCapture()) { bIsDragging = true; if ((int32)PanningMode & (int32)EPanningMode::Horizontal) { const double StartTime = ViewportStartTimeOnButtonDown + static_cast(MousePositionOnButtonDown.X - MousePosition.X) / Viewport.GetScaleX(); ScrollAtTime(StartTime); } if ((int32)PanningMode & (int32)EPanningMode::Vertical) { const float ScrollPosY = ViewportScrollPosYOnButtonDown + (MousePositionOnButtonDown.Y - MousePosition.Y); ScrollAtPosY(ScrollPosY); } } } else if (bIsSelecting) { if (HasMouseCapture()) { bIsDragging = true; SelectionStartTime = Viewport.SlateUnitsToTime(MousePositionOnButtonDown.X); SelectionEndTime = Viewport.SlateUnitsToTime(MousePosition.X); if (SelectionStartTime > SelectionEndTime) { double Temp = SelectionStartTime; SelectionStartTime = SelectionEndTime; SelectionEndTime = Temp; } LastSelectionType = ESelectionType::TimeRange; RaiseSelectionChanging(); } } else if (TimeRulerTrack->IsScrubbing()) { if (HasMouseCapture()) { bIsDragging = true; TSharedRef ScrubbingTimeMarker = TimeRulerTrack->GetScrubbingTimeMarker(); ScrubbingTimeMarker->SetTime(Viewport.SlateUnitsToTime(MousePosition.X)); RaiseTimeMarkerChanging(); } } else { UpdateHoveredTimingEvent(MousePosition.X, MousePosition.Y); } Reply = FReply::Handled(); } return Reply; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::OnMouseLeave(const FPointerEvent& MouseEvent) { if (!HasMouseCapture()) { // No longer dragging (unless we have mouse capture). bIsDragging = false; bIsPanning = false; bIsSelecting = false; bIsLMB_Pressed = false; bIsRMB_Pressed = false; MousePosition = FVector2D::ZeroVector; if (HoveredTrack.IsValid()) { HoveredTrack.Reset(); OnHoveredTrackChangedDelegate.Broadcast(HoveredTrack); } if (HoveredEvent.IsValid()) { HoveredEvent.Reset(); OnHoveredEventChangedDelegate.Broadcast(HoveredEvent); } Tooltip.SetDesiredOpacity(0.0f); } } //////////////////////////////////////////////////////////////////////////////////////////////////// FReply STimingView::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { if (MouseEvent.GetModifierKeys().IsShiftDown()) { // MousePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()); if (GraphTrack->IsVisible() && MousePosition.Y >= GraphTrack->GetPosY() && MousePosition.Y < GraphTrack->GetPosY() + GraphTrack->GetHeight()) { // Zoom in/out vertically. const double Delta = MouseEvent.GetWheelDelta(); constexpr double ZoomStep = 0.25; // as percent constexpr double MinScaleY = 0.0001; constexpr double MaxScaleY = 1.0e10; double ScaleY = GraphTrack->GetSharedValueViewport().GetScaleY(); if (Delta > 0) { ScaleY *= FMath::Pow(1.0 + ZoomStep, Delta); if (ScaleY > MaxScaleY) { ScaleY = MaxScaleY; } } else { ScaleY *= FMath::Pow(1.0 / (1.0 + ZoomStep), -Delta); if (ScaleY < MinScaleY) { ScaleY = MinScaleY; } } GraphTrack->GetSharedValueViewport().SetScaleY(ScaleY); for (const TSharedPtr& Series: GraphTrack->GetSeries()) { if (Series->IsUsingSharedViewport()) { Series->SetScaleY(ScaleY); Series->SetDirtyFlag(); } } } else { // Scroll vertically. constexpr float ScrollSpeedY = 16.0f * 3; const float NewScrollPosY = Viewport.GetScrollPosY() - ScrollSpeedY * MouseEvent.GetWheelDelta(); ScrollAtPosY(EnforceVerticalScrollLimits(NewScrollPosY)); } } else if (MouseEvent.GetModifierKeys().IsControlDown()) { // Scroll horizontally. const double ScrollSpeedX = Viewport.GetDurationForViewportDX(16.0 * 3); const double NewStartTime = Viewport.GetStartTime() - ScrollSpeedX * MouseEvent.GetWheelDelta(); ScrollAtTime(EnforceHorizontalScrollLimits(NewStartTime)); } else { // Zoom in/out horizontally. const double Delta = MouseEvent.GetWheelDelta(); // MousePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()); if (Viewport.RelativeZoomWithFixedX(Delta, MousePosition.X)) { UpdateHorizontalScrollBar(); } } return FReply::Handled(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::OnDragEnter(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) { SCompoundWidget::OnDragEnter(MyGeometry, DragDropEvent); // TSharedPtr Operation = DragDropEvent.GetOperationAs(); // if (Operation.IsValid()) //{ // Operation->ShowOK(); // } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::OnDragLeave(const FDragDropEvent& DragDropEvent) { SCompoundWidget::OnDragLeave(DragDropEvent); // TSharedPtr Operation = DragDropEvent.GetOperationAs(); // if (Operation.IsValid()) //{ // Operation->ShowError(); // } } //////////////////////////////////////////////////////////////////////////////////////////////////// FReply STimingView::OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) { return SCompoundWidget::OnDragOver(MyGeometry, DragDropEvent); } //////////////////////////////////////////////////////////////////////////////////////////////////// FReply STimingView::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) { // TSharedPtr Operation = DragDropEvent.GetOperationAs(); // if (Operation.IsValid()) //{ // return FReply::Handled(); // } return SCompoundWidget::OnDrop(MyGeometry, DragDropEvent); } //////////////////////////////////////////////////////////////////////////////////////////////////// FCursorReply STimingView::OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const { if (bIsPanning) { if (bIsDragging) { // return FCursorReply::Cursor(EMouseCursor::GrabHandClosed); return FCursorReply::Cursor(EMouseCursor::GrabHand); } } else if (bIsSelecting) { if (bIsDragging) { return FCursorReply::Cursor(EMouseCursor::ResizeLeftRight); } } else if (bIsSpaceBarKeyPressed) { return FCursorReply::Cursor(EMouseCursor::GrabHand); } return FCursorReply::Unhandled(); } //////////////////////////////////////////////////////////////////////////////////////////////////// FReply STimingView::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) { if (InKeyEvent.GetKey() == EKeys::B) { // Toggle Bookmarks. if (MarkersTrack->IsVisible()) { if (!MarkersTrack->IsBookmarksTrack()) { SetDrawOnlyBookmarks(true); } else { SetTimeMarkersVisible(false); } } else { SetTimeMarkersVisible(true); SetDrawOnlyBookmarks(true); } return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::M) { // Toggle Time Markers. if (MarkersTrack->IsVisible()) { if (MarkersTrack->IsBookmarksTrack()) { SetDrawOnlyBookmarks(false); } else { SetTimeMarkersVisible(false); } } else { SetTimeMarkersVisible(true); SetDrawOnlyBookmarks(false); } return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::F) { FrameSelection(); return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::C) { if (InKeyEvent.GetModifierKeys().IsControlDown()) { if (SelectedEvent.IsValid()) { SelectedEvent->GetTrack()->OnClipboardCopyEvent(*SelectedEvent); } } else { Viewport.SwitchLayoutCompactMode(); return FReply::Handled(); } } else if (InKeyEvent.GetKey() == EKeys::V) { ToggleAutoHideEmptyTracks(); return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::Equals || InKeyEvent.GetKey() == EKeys::Add) { // Zoom In const double ScaleX = Viewport.GetScaleX() * 1.25; if (Viewport.ZoomWithFixedX(ScaleX, Viewport.GetWidth() / 2)) { UpdateHorizontalScrollBar(); } return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::Hyphen || InKeyEvent.GetKey() == EKeys::Subtract) { // Zoom Out const double ScaleX = Viewport.GetScaleX() / 1.25; if (Viewport.ZoomWithFixedX(ScaleX, Viewport.GetWidth() / 2)) { UpdateHorizontalScrollBar(); } return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::Left) { if (InKeyEvent.GetModifierKeys().IsControlDown()) { // Scroll Left const double NewStartTime = Viewport.GetStartTime() - Viewport.GetDuration() * 0.05; ScrollAtTime(EnforceHorizontalScrollLimits(NewStartTime)); } else { SelectLeftTimingEvent(); } return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::Right) { if (InKeyEvent.GetModifierKeys().IsControlDown()) { // Scroll Right const double NewStartTime = Viewport.GetStartTime() + Viewport.GetDuration() * 0.05; ScrollAtTime(EnforceHorizontalScrollLimits(NewStartTime)); } else { SelectRightTimingEvent(); } return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::Up) { if (InKeyEvent.GetModifierKeys().IsControlDown()) { // Scroll Up const float NewScrollPosY = Viewport.GetScrollPosY() - 16.0f * 3; ScrollAtPosY(EnforceVerticalScrollLimits(NewScrollPosY)); } else { SelectUpTimingEvent(); } return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::Down) { if (InKeyEvent.GetModifierKeys().IsControlDown()) { // Scroll Down const float NewScrollPosY = Viewport.GetScrollPosY() + 16.0f * 3; ScrollAtPosY(EnforceVerticalScrollLimits(NewScrollPosY)); } else { SelectDownTimingEvent(); } return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::Enter) { if (SelectedEvent.IsValid()) { const double Duration = Viewport.RestrictDuration(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime()); SelectTimeInterval(SelectedEvent->GetStartTime(), Duration); } return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::SpaceBar) { bIsSpaceBarKeyPressed = true; FSlateApplication::Get().QueryCursor(); return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::D) // debug: toggles down-sampling on/off { FTimingEventsTrack::bUseDownSampling = !FTimingEventsTrack::bUseDownSampling; Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated); return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::G) { ShowHideGraphTrack_Execute(); return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::R) { FrameSharedState->ShowHideAllFrameTracks(); return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::Y) { ThreadTimingSharedState->ShowHideAllGpuTracks(); return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::U) { ThreadTimingSharedState->ShowHideAllCpuTracks(); return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::L) { LoadingSharedState->ShowHideAllLoadingTracks(); return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::I) { FileActivitySharedState->ShowHideAllIoTracks(); return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::O) { FileActivitySharedState->ToggleBackgroundEvents(); return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::One) { LoadingSharedState->SetColorSchema(0); return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::Two) { LoadingSharedState->SetColorSchema(1); return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::Three) { LoadingSharedState->SetColorSchema(2); return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::Four) { LoadingSharedState->SetColorSchema(3); return FReply::Handled(); } #if ACTIVATE_BENCHMARK else if (InKeyEvent.GetKey() == EKeys::Z) { FTimingProfilerTests::FCheckValues CheckValues; FTimingProfilerTests::RunEnumerateBenchmark(FTimingProfilerTests::FEnumerateTestParams(), CheckValues); return FReply::Handled(); } #endif return SCompoundWidget::OnKeyDown(MyGeometry, InKeyEvent); } //////////////////////////////////////////////////////////////////////////////////////////////////// FReply STimingView::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) { if (InKeyEvent.GetKey() == EKeys::SpaceBar) { bIsSpaceBarKeyPressed = false; FSlateApplication::Get().QueryCursor(); return FReply::Handled(); } return SCompoundWidget::OnKeyUp(MyGeometry, InKeyEvent); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::ShowContextMenu(const FPointerEvent& MouseEvent) { // TSharedPtr ProfilerCommandList = FTimingProfilerManager::Get()->GetCommandList(); // const FTimingProfilerCommands& ProfilerCommands = FTimingProfilerManager::GetCommands(); // const FTimingViewCommands& Commands = FTimingViewCommands::Get(); const bool bShouldCloseWindowAfterMenuSelection = true; // FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, ProfilerCommandList); FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, NULL); if (HoveredTrack.IsValid()) { MenuBuilder.BeginSection(TEXT("Track"), FText::FromString(HoveredTrack->GetName())); CreateTrackLocationMenu(MenuBuilder, HoveredTrack.ToSharedRef()); MenuBuilder.EndSection(); if (SelectionStartTime != SelectionEndTime || SelectedEvent.IsValid()) { // Add export menu options if there's a selected event MenuBuilder.BeginSection(TEXT("EventExport"), LOCTEXT("ContextMenu_EventExportSection", "Export Event")); { MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_ExportEvent_AllTrack", "Export to JSON (All Track)"), LOCTEXT("ContextMenu_ExportEvent_AllTrack_Desc", "Export this event and its children from all track to JSON file."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &STimingView::ExportSelectedEventToJSON)), NAME_None, EUserInterfaceActionType::Button); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_ExportEvent_CurrentTrack", "Export to JSON (Current Track)"), LOCTEXT("ContextMenu_ExportEvent_CurrentTrack_Desc", "Export this event and its children from current track to JSON file."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &STimingView::ExportSelectedEventToJSON_CurrentTrack)), NAME_None, EUserInterfaceActionType::Button); } MenuBuilder.EndSection(); } HoveredTrack->BuildContextMenu(MenuBuilder); } else { MenuBuilder.BeginSection(TEXT("Empty")); { MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_NA", "N/A"), LOCTEXT("ContextMenu_NA_Desc", "No actions available."), FSlateIcon(), FUIAction(FExecuteAction(), FCanExecuteAction::CreateLambda([]() { return false; })), NAME_None, EUserInterfaceActionType::Button); } MenuBuilder.EndSection(); } // MenuBuilder.BeginSection(TEXT("Misc"), LOCTEXT("Miscellaneous", "Miscellaneous")); //{ // MenuBuilder.AddMenuEntry(Commands.ShowAllGpuTracks); // MenuBuilder.AddMenuEntry(Commands.ShowAllCpuTracks); // MenuBuilder.AddMenuEntry(ProfilerCommands.ToggleTimersViewVisibility); // MenuBuilder.AddMenuEntry(ProfilerCommands.ToggleStatsCountersViewVisibility); // } // MenuBuilder.EndSection(); TSharedRef MenuWidget = MenuBuilder.MakeWidget(); FWidgetPath EventPath = MouseEvent.GetEventPath() != nullptr ? *MouseEvent.GetEventPath() : FWidgetPath(); const FVector2D ScreenSpacePosition = MouseEvent.GetScreenSpacePosition(); FSlateApplication::Get().PushMenu(SharedThis(this), EventPath, MenuWidget, ScreenSpacePosition, FPopupTransitionEffect::ContextMenu); } //////////////////////////////////////////////////////////////////////////////////////////////////// FString STimingView::GetSelectedEventName() const { FString EventName; if (SelectedEvent.IsValid() && SelectedEvent->Is()) { const FThreadTrackEvent& ThreadEvent = SelectedEvent->As(); // Get the analysis session TSharedPtr Session = FInsightsManager::Get()->GetSession(); if (Session.IsValid()) { Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); const Trace::ITimingProfilerProvider* TimingProfilerProvider = Trace::ReadTimingProfilerProvider(*Session.Get()); if (TimingProfilerProvider) { const Trace::ITimingProfilerTimerReader* TimerReader = nullptr; TimingProfilerProvider->ReadTimers([&TimerReader](const Trace::ITimingProfilerTimerReader& Out) { TimerReader = &Out; }); if (TimerReader) { const Trace::FTimingProfilerTimer* Timer = TimerReader->GetTimer(ThreadEvent.GetTimerIndex()); if (Timer != nullptr && Timer->Name != nullptr) { EventName = Timer->Name; } } } } } if (!EventName.IsEmpty()) { FString SanitizedFileName = EventName; SanitizedFileName = FPaths::MakeValidFileName(SanitizedFileName); SanitizedFileName.ReplaceInline(TEXT(" "), TEXT("_")); return SanitizedFileName; } EventName = FString::Printf(TEXT("Timeline_%.3f-%.3f"), SelectionStartTime, SelectionEndTime); return EventName; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::ExportSelectedEventToJSON() { // Get the analysis session TSharedPtr Session = FInsightsManager::Get()->GetSession(); if (!Session.IsValid()) { UE_LOG(LogTemp, Error, TEXT("No analysis session available for export.")); return; } TraceTimer::FExportFilter ExportFilter(TEXT("ExportSelectedEventToJSON")); // Get the selected event's time range ExportFilter.StartTime = SelectedEvent.IsValid() ? SelectedEvent->GetStartTime() : SelectionStartTime; ExportFilter.EndTime = SelectedEvent.IsValid() ? SelectedEvent->GetEndTime() : SelectionEndTime; // Get the selected event's name FString SanitizedEventName = GetSelectedEventName(); // Prompt user for save location TArray OutFilenames; bool bFileSaved = false; if (IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get()) { FString DefaultFileName = FString::Printf(TEXT("Export_%s_AllTrack.json"), *SanitizedEventName); bFileSaved = DesktopPlatform->SaveFileDialog( FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), TEXT("Export Event to JSON"), FPaths::ProjectSavedDir(), DefaultFileName, TEXT("JSON Files (*.json)|*.json"), 0, // No flags OutFilenames); } if (bFileSaved && OutFilenames.Num() > 0) { const FString& OutputFilename = OutFilenames[0]; // Use FMinimalTimerExporter to export events in the selected time range (all threads) if (FMinimalTimerExporter::ExportTimingEventsToJSON(*Session, OutputFilename, ExportFilter)) { UE_LOG(LogTemp, Display, TEXT("Successfully exported event to %s"), *OutputFilename); } else { UE_LOG(LogTemp, Error, TEXT("Failed to export event to %s"), *OutputFilename); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::ExportSelectedEventToJSON_CurrentTrack() { // Get the analysis session TSharedPtr Session = FInsightsManager::Get()->GetSession(); if (!Session.IsValid()) { UE_LOG(LogTemp, Error, TEXT("No analysis session available for export.")); return; } TraceTimer::FExportFilter ExportFilter(TEXT("ExportSelectedEventToJSON_CurrentTrack")); // Get the selected event's time range ExportFilter.StartTime = SelectedEvent.IsValid() ? SelectedEvent->GetStartTime() : SelectionStartTime; ExportFilter.EndTime = SelectedEvent.IsValid() ? SelectedEvent->GetEndTime() : SelectionEndTime; FString SanitizedEventName = GetSelectedEventName(); // Prompt user for save location TArray OutFilenames; bool bFileSaved = false; if (IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get()) { FString DefaultFileName = FString::Printf(TEXT("Export_%s_CurrentTrack.json"), *SanitizedEventName); bFileSaved = DesktopPlatform->SaveFileDialog( FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), TEXT("Export Event to JSON (Current Track)"), FPaths::ProjectSavedDir(), DefaultFileName, TEXT("JSON Files (*.json)|*.json"), 0, // No flags OutFilenames); } if (bFileSaved && OutFilenames.Num() > 0) { const FString& OutputFilename = OutFilenames[0]; // Get the current track ID from the selected event's track FBaseTimingTrack* TrackPtr = SelectedTrack.IsValid() ? SelectedTrack.Get() : HoveredTrack.Get(); if (const FThreadTimingTrack* ThreadTrack = static_cast(TrackPtr)) { ExportFilter.TrackIndex = ThreadTrack->GetTimelineIndex(); } // Use FMinimalTimerExporter to export events in the selected time range (all threads) if (FMinimalTimerExporter::ExportTimingEventsToJSON(*Session, OutputFilename, ExportFilter)) { UE_LOG(LogTemp, Display, TEXT("Successfully exported event to %s"), *OutputFilename); } else { UE_LOG(LogTemp, Error, TEXT("Failed to export event to %s"), *OutputFilename); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::CreateTrackLocationMenu(FMenuBuilder& MenuBuilder, TSharedRef Track) { if (EnumHasAnyFlags(Track->GetValidLocations(), ETimingTrackLocation::TopDocked)) { if (Track->GetLocation() == ETimingTrackLocation::TopDocked) { MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_LocationTopDocked", "Location: Top Docked Tracks"), LOCTEXT("ContextMenu_LocationTopDocked_Desc", "This track is in the list of top docked tracks."), FSlateIcon(), FUIAction(FExecuteAction(), FCanExecuteAction::CreateLambda([] { return false; })), NAME_None, EUserInterfaceActionType::Button); } else { MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_DockToTop", "Dock To Top"), LOCTEXT("ContextMenu_DockToTop_Desc", "Dock this track to the top."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &STimingView::ChangeTrackLocation, Track, ETimingTrackLocation::TopDocked), FCanExecuteAction::CreateSP(this, &STimingView::CanChangeTrackLocation, Track, ETimingTrackLocation::TopDocked)), NAME_None, EUserInterfaceActionType::Button); } } if (EnumHasAnyFlags(Track->GetValidLocations(), ETimingTrackLocation::Scrollable)) { if (Track->GetLocation() == ETimingTrackLocation::Scrollable) { MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_LocationScrollable", "Location: Scrollable Tracks"), LOCTEXT("ContextMenu_LocationScrollable_Desc", "This track is in the list of scrollable tracks."), FSlateIcon(), FUIAction(FExecuteAction(), FCanExecuteAction::CreateLambda([] { return false; })), NAME_None, EUserInterfaceActionType::Button); } else { MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_MoveToScrollable", "Move to Scrollable Tracks"), LOCTEXT("ContextMenu_MoveToScrollable_Desc", "Move this track to the list of scrollable tracks."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &STimingView::ChangeTrackLocation, Track, ETimingTrackLocation::Scrollable), FCanExecuteAction::CreateSP(this, &STimingView::CanChangeTrackLocation, Track, ETimingTrackLocation::Scrollable)), NAME_None, EUserInterfaceActionType::Button); } } if (EnumHasAnyFlags(Track->GetValidLocations(), ETimingTrackLocation::BottomDocked)) { if (Track->GetLocation() == ETimingTrackLocation::BottomDocked) { MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_LocationBottomDocked", "Location: Bottom Docked Tracks"), LOCTEXT("ContextMenu_LocationBottomDocked_Desc", "This track is in the list of bottom docked tracks."), FSlateIcon(), FUIAction(FExecuteAction(), FCanExecuteAction::CreateLambda([] { return false; })), NAME_None, EUserInterfaceActionType::Button); } else { MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_DockToBottom", "Dock To Bottom"), LOCTEXT("ContextMenu_DockToBottom_Desc", "Dock this track to the bottom."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &STimingView::ChangeTrackLocation, Track, ETimingTrackLocation::BottomDocked), FCanExecuteAction::CreateSP(this, &STimingView::CanChangeTrackLocation, Track, ETimingTrackLocation::BottomDocked)), NAME_None, EUserInterfaceActionType::Button); } } if (EnumHasAnyFlags(Track->GetValidLocations(), ETimingTrackLocation::Foreground)) { if (Track->GetLocation() == ETimingTrackLocation::Foreground) { MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_LocationForeground", "Location: Foreground Tracks"), LOCTEXT("ContextMenu_LocationForeground_Desc", "This track is in the list of foreground tracks."), FSlateIcon(), FUIAction(FExecuteAction(), FCanExecuteAction::CreateLambda([] { return false; })), NAME_None, EUserInterfaceActionType::Button); } else { MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_MoveToForeground", "Move to Foreground Tracks"), LOCTEXT("ContextMenu_MoveToForeground_Desc", "Move this track to the list of foreground tracks."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &STimingView::ChangeTrackLocation, Track, ETimingTrackLocation::Foreground), FCanExecuteAction::CreateSP(this, &STimingView::CanChangeTrackLocation, Track, ETimingTrackLocation::Foreground)), NAME_None, EUserInterfaceActionType::Button); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::ChangeTrackLocation(TSharedRef Track, ETimingTrackLocation NewLocation) { if (ensure(CanChangeTrackLocation(Track, NewLocation))) { switch (Track->GetLocation()) { case ETimingTrackLocation::Scrollable: ensure(RemoveScrollableTrack(Track)); break; case ETimingTrackLocation::TopDocked: ensure(RemoveTopDockedTrack(Track)); break; case ETimingTrackLocation::BottomDocked: ensure(RemoveBottomDockedTrack(Track)); break; case ETimingTrackLocation::Foreground: ensure(RemoveForegroundTrack(Track)); break; } switch (NewLocation) { case ETimingTrackLocation::Scrollable: AddScrollableTrack(Track); break; case ETimingTrackLocation::TopDocked: AddTopDockedTrack(Track); break; case ETimingTrackLocation::BottomDocked: AddBottomDockedTrack(Track); break; case ETimingTrackLocation::Foreground: AddForegroundTrack(Track); break; } OnTrackVisibilityChanged(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimingView::CanChangeTrackLocation(TSharedRef Track, ETimingTrackLocation NewLocation) const { return EnumHasAnyFlags(Track->GetValidLocations(), NewLocation) && Track->GetLocation() != NewLocation; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::BindCommands() { // FTimingViewCommands::Register(); // const FTimingViewCommands& Commands = FTimingViewCommands::Get(); // // TSharedPtr CommandList = FTimingProfilerManager::Get()->GetCommandList(); // CommandList->MapAction( // Commands.ShowAllGpuTracks, // FExecuteAction::CreateSP(this, &STimingView::ShowHideAllGpuTracks_Execute), // FCanExecuteAction(), //FCanExecuteAction::CreateLambda([] { return true; }), // FIsActionChecked::CreateSP(this, &STimingView::ShowHideAllGpuTracks_IsChecked)); // CommandList->MapAction( // Commands.ShowAllCpuTracks, // FExecuteAction::CreateSP(this, &STimingView::ShowHideAllCpuTracks_Execute), // FCanExecuteAction(), //FCanExecuteAction::CreateLambda([] { return true; }), // FIsActionChecked::CreateSP(this, &STimingView::ShowHideAllCpuTracks_IsChecked)); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::AutoScroll_OnCheckStateChanged(ECheckBoxState NewRadioState) { bAutoScroll = (NewRadioState == ECheckBoxState::Checked); Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated); } //////////////////////////////////////////////////////////////////////////////////////////////////// ECheckBoxState STimingView::AutoScroll_IsChecked() const { return bAutoScroll ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::AutoScrollFrameAligned_Execute() { bIsAutoScrollFrameAligned = !bIsAutoScrollFrameAligned; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimingView::AutoScrollFrameAligned_IsChecked() const { return bIsAutoScrollFrameAligned; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::AutoScrollFrameType_Execute(ETraceFrameType FrameType) { AutoScrollFrameType = FrameType; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimingView::AutoScrollFrameType_CanExecute(ETraceFrameType FrameType) const { return bIsAutoScrollFrameAligned; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimingView::AutoScrollFrameType_IsChecked(ETraceFrameType FrameType) const { return AutoScrollFrameType == FrameType; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::AutoScrollViewportOffset_Execute(double Percent) { AutoScrollViewportOffsetPercent = Percent; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimingView::AutoScrollViewportOffset_IsChecked(double Percent) const { return AutoScrollViewportOffsetPercent == Percent; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::AutoScrollDelay_Execute(double Delay) { AutoScrollMinDelay = Delay; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimingView::AutoScrollDelay_IsChecked(double Delay) const { return AutoScrollMinDelay == Delay; } //////////////////////////////////////////////////////////////////////////////////////////////////// double STimingView::EnforceHorizontalScrollLimits(const double InStartTime) { double NewStartTime = InStartTime; double MinT, MaxT; Viewport.GetHorizontalScrollLimits(MinT, MaxT); if (NewStartTime > MaxT) { NewStartTime = MaxT; OverscrollRight = 1.0f; } if (NewStartTime < MinT) { NewStartTime = MinT; OverscrollLeft = 1.0f; } return NewStartTime; } //////////////////////////////////////////////////////////////////////////////////////////////////// float STimingView::EnforceVerticalScrollLimits(const float InScrollPosY) { float NewScrollPosY = InScrollPosY; const float DY = Viewport.GetScrollHeight() - Viewport.GetScrollableAreaHeight() + 7.0f; // +7 is to allow some space for the horizontal scrollbar const float MinY = FMath::Min(DY, 0.0f); const float MaxY = DY - MinY; if (NewScrollPosY > MaxY) { NewScrollPosY = MaxY; OverscrollBottom = 1.0f; } if (NewScrollPosY < MinY) { NewScrollPosY = MinY; OverscrollTop = 1.0f; } return NewScrollPosY; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::HorizontalScrollBar_OnUserScrolled(float ScrollOffset) { // Disable auto-scroll if user starts scrolling with horizontal scrollbar. bAutoScroll = false; Viewport.OnUserScrolled(HorizontalScrollBar, ScrollOffset); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::UpdateHorizontalScrollBar() { Viewport.UpdateScrollBar(HorizontalScrollBar); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::VerticalScrollBar_OnUserScrolled(float ScrollOffset) { Viewport.OnUserScrolledY(VerticalScrollBar, ScrollOffset); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::UpdateVerticalScrollBar() { Viewport.UpdateScrollBarY(VerticalScrollBar); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::ScrollAtPosY(float ScrollPosY) { if (ScrollPosY != Viewport.GetScrollPosY()) { Viewport.SetScrollPosY(ScrollPosY); UpdateVerticalScrollBar(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::ScrollAtTime(double StartTime) { if (Viewport.ScrollAtTime(StartTime)) { UpdateHorizontalScrollBar(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::CenterOnTimeInterval(double IntervalStartTime, double IntervalDuration) { if (Viewport.CenterOnTimeInterval(IntervalStartTime, IntervalDuration)) { Viewport.EnforceHorizontalScrollLimits(1.0); // 1.0 is to disable interpolation UpdateHorizontalScrollBar(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::BringIntoView(double StartTime, double EndTime) { EndTime = Viewport.RestrictEndTime(EndTime); // Increase interval with 8% (of view size) on each side. const double DT = Viewport.GetDuration() * 0.08; StartTime -= DT; EndTime += DT; double NewStartTime = Viewport.GetStartTime(); if (EndTime > Viewport.GetEndTime()) { NewStartTime += EndTime - Viewport.GetEndTime(); } if (StartTime < NewStartTime) { NewStartTime = StartTime; } ScrollAtTime(NewStartTime); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::SelectTimeInterval(double IntervalStartTime, double IntervalDuration) { SelectionStartTime = IntervalStartTime; SelectionEndTime = IntervalStartTime + IntervalDuration; LastSelectionType = ESelectionType::TimeRange; RaiseSelectionChanged(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::RaiseSelectionChanging() { OnSelectionChangedDelegate.Broadcast(Insights::ETimeChangedFlags::Interactive, SelectionStartTime, SelectionEndTime); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::RaiseSelectionChanged() { OnSelectionChangedDelegate.Broadcast(Insights::ETimeChangedFlags::None, SelectionStartTime, SelectionEndTime); FTimingProfilerManager::Get()->SetSelectedTimeRange(SelectionStartTime, SelectionEndTime); if (SelectionStartTime < SelectionEndTime) { UpdateAggregatedStats(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::RaiseTimeMarkerChanging() { const double TimeMarker = GetTimeMarker(); OnTimeMarkerChangedDelegate.Broadcast(Insights::ETimeChangedFlags::Interactive, TimeMarker); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::RaiseTimeMarkerChanged() { const double TimeMarker = GetTimeMarker(); OnTimeMarkerChangedDelegate.Broadcast(Insights::ETimeChangedFlags::None, TimeMarker); } //////////////////////////////////////////////////////////////////////////////////////////////////// double STimingView::GetTimeMarker() const { return DefaultTimeMarker->GetTime(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::SetTimeMarker(double InMarkerTime) { DefaultTimeMarker->SetTime(InMarkerTime); RaiseTimeMarkerChanged(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::AddOverlayWidget(const TSharedRef& InWidget) { if (ExtensionOverlay.IsValid()) { ExtensionOverlay->AddSlot() [InWidget]; } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::UpdateAggregatedStats() { if (bAssetLoadingMode) { TSharedPtr LoadingProfilerWnd = FLoadingProfilerManager::Get()->GetProfilerWindow(); if (LoadingProfilerWnd.IsValid()) { LoadingProfilerWnd->UpdateTableTreeViews(); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::SetAndCenterOnTimeMarker(double Time) { SetTimeMarker(Time); double MinT, MaxT; Viewport.GetHorizontalScrollLimits(MinT, MaxT); const double ViewportDuration = Viewport.GetDuration(); MinT += ViewportDuration / 2; MaxT += ViewportDuration / 2; Time = FMath::Clamp(Time, MinT, MaxT); Time = Viewport.AlignTimeToPixel(Time); CenterOnTimeInterval(Time, 0.0); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::SelectToTimeMarker(double Time) { const double TimeMarker = GetTimeMarker(); if (TimeMarker < Time) { SelectTimeInterval(TimeMarker, Time - TimeMarker); } else { SelectTimeInterval(Time, TimeMarker - Time); } SetTimeMarker(Time); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::SetTimeMarkersVisible(bool bIsMarkersTrackVisible) { if (MarkersTrack->IsVisible() != bIsMarkersTrackVisible) { MarkersTrack->SetVisibilityFlag(bIsMarkersTrackVisible); if (MarkersTrack->IsVisible()) { if (Viewport.GetScrollPosY() != 0.0f) { UE_LOG(TimingProfiler, Log, TEXT("SetTimeMarkersVisible!!!")); Viewport.SetScrollPosY(Viewport.GetScrollPosY() + MarkersTrack->GetHeight()); } MarkersTrack->SetDirtyFlag(); } else { UE_LOG(TimingProfiler, Log, TEXT("SetTimeMarkersVisible!!!")); Viewport.SetScrollPosY(Viewport.GetScrollPosY() - MarkersTrack->GetHeight()); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::SetDrawOnlyBookmarks(bool bIsBookmarksTrack) { if (MarkersTrack->IsBookmarksTrack() != bIsBookmarksTrack) { const float PrevHeight = MarkersTrack->GetHeight(); MarkersTrack->SetBookmarksTrackFlag(bIsBookmarksTrack); if (MarkersTrack->IsVisible()) { if (Viewport.GetScrollPosY() != 0.0f) { UE_LOG(TimingProfiler, Log, TEXT("SetDrawOnlyBookmarks!!!")); Viewport.SetScrollPosY(Viewport.GetScrollPosY() + MarkersTrack->GetHeight() - PrevHeight); } MarkersTrack->SetDirtyFlag(); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// const TSharedPtr STimingView::GetTrackAt(float InPosX, float InPosY) const { TSharedPtr FoundTrack; if (InPosY < 0.0f) { // above viewport } else if (InPosY < Viewport.GetTopOffset()) { // Top Docked Tracks for (const TSharedPtr& TrackPtr: TopDockedTracks) { const FBaseTimingTrack& Track = *TrackPtr; if (TrackPtr->IsVisible()) { if (InPosY >= Track.GetPosY() && InPosY < Track.GetPosY() + Track.GetHeight()) { FoundTrack = TrackPtr; break; } } } } else if (InPosY < Viewport.GetHeight() - Viewport.GetBottomOffset()) { // Scrollable Tracks for (const TSharedPtr& TrackPtr: ScrollableTracks) { const FBaseTimingTrack& Track = *TrackPtr; if (Track.IsVisible()) { if (InPosY >= Track.GetPosY() && InPosY < Track.GetPosY() + Track.GetHeight()) { FoundTrack = TrackPtr; break; } } } } else if (InPosY < Viewport.GetHeight()) { // Bottom Docked Tracks for (const TSharedPtr& TrackPtr: BottomDockedTracks) { const FBaseTimingTrack& Track = *TrackPtr; if (TrackPtr->IsVisible()) { if (InPosY >= Track.GetPosY() && InPosY < Track.GetPosY() + Track.GetHeight()) { FoundTrack = TrackPtr; break; } } } } else { // below viewport } return FoundTrack; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::UpdateHoveredTimingEvent(float InMousePosX, float InMousePosY) { TSharedPtr NewHoveredTrack = GetTrackAt(InMousePosX, InMousePosY); if (NewHoveredTrack != HoveredTrack) { HoveredTrack = NewHoveredTrack; OnHoveredTrackChangedDelegate.Broadcast(HoveredTrack); } TSharedPtr NewHoveredEvent; if (HoveredTrack.IsValid()) { FStopwatch Stopwatch; Stopwatch.Start(); NewHoveredEvent = HoveredTrack->GetEvent(InMousePosX, InMousePosY, Viewport); Stopwatch.Stop(); const double DT = Stopwatch.GetAccumulatedTime(); if (DT > 0.001) { UE_LOG(TimingProfiler, Log, TEXT("HoveredTrack [%g, %g] GetEvent: %.1f ms"), InMousePosX, InMousePosY, DT * 1000.0); } } if (NewHoveredEvent.IsValid()) { if (!HoveredEvent.IsValid() || !NewHoveredEvent->Equals(*HoveredEvent)) { FStopwatch Stopwatch; Stopwatch.Start(); HoveredEvent = NewHoveredEvent; ensure(HoveredTrack == HoveredEvent->GetTrack()); HoveredTrack->UpdateEventStats(const_cast(*HoveredEvent)); Stopwatch.Update(); const double T1 = Stopwatch.GetAccumulatedTime(); HoveredTrack->InitTooltip(Tooltip, *HoveredEvent); Stopwatch.Update(); const double T2 = Stopwatch.GetAccumulatedTime(); OnHoveredEventChangedDelegate.Broadcast(HoveredEvent); Stopwatch.Update(); const double T3 = Stopwatch.GetAccumulatedTime(); if (T3 > 0.001) { UE_LOG(TimingProfiler, Log, TEXT("HoveredTrack [%g, %g] Tooltip: %.1f ms (%.1f + %.1f + %.1f)"), InMousePosX, InMousePosY, T3 * 1000.0, T1 * 1000.0, (T2 - T1) * 1000.0, (T3 - T2) * 1000.0); } } Tooltip.SetDesiredOpacity(1.0f); } else { if (HoveredEvent.IsValid()) { HoveredEvent.Reset(); OnHoveredEventChangedDelegate.Broadcast(HoveredEvent); } Tooltip.SetDesiredOpacity(0.0f); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::OnSelectedTimingEventChanged() { if (!bAssetLoadingMode) { if (SelectedEvent.IsValid()) { SelectedEvent->GetTrack()->UpdateEventStats(const_cast(*SelectedEvent)); SelectedEvent->GetTrack()->OnEventSelected(*SelectedEvent); } } OnSelectedEventChangedDelegate.Broadcast(SelectedEvent); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::SelectHoveredTimingTrack() { if (SelectedTrack != HoveredTrack) { SelectedTrack = HoveredTrack; OnSelectedTrackChangedDelegate.Broadcast(SelectedTrack); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::SelectHoveredTimingEvent() { if (SelectedEvent != HoveredEvent) { SelectedEvent = HoveredEvent; if (SelectedEvent.IsValid()) { LastSelectionType = ESelectionType::TimingEvent; BringIntoView(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime()); } OnSelectedTimingEventChanged(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::SelectLeftTimingEvent() { if (SelectedEvent.IsValid()) { const uint32 Depth = SelectedEvent->GetDepth(); const double StartTime = SelectedEvent->GetStartTime(); const double EndTime = SelectedEvent->GetEndTime(); auto EventFilter = [Depth, StartTime, EndTime](double EventStartTime, double EventEndTime, uint32 EventDepth) { return EventDepth == Depth && (EventStartTime < StartTime || EventEndTime < EndTime); }; const TSharedPtr LeftEvent = SelectedEvent->GetTrack()->SearchEvent( FTimingEventSearchParameters(0.0, StartTime, ETimingEventSearchFlags::SearchAll, EventFilter)); if (LeftEvent.IsValid()) { SelectedEvent = LeftEvent; BringIntoView(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime()); OnSelectedTimingEventChanged(); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::SelectRightTimingEvent() { if (SelectedEvent.IsValid()) { const uint32 Depth = SelectedEvent->GetDepth(); const double StartTime = SelectedEvent->GetStartTime(); const double EndTime = SelectedEvent->GetEndTime(); auto EventFilter = [Depth, StartTime, EndTime](double EventStartTime, double EventEndTime, uint32 EventDepth) { return EventDepth == Depth && (EventStartTime > StartTime || EventEndTime > EndTime); }; const TSharedPtr RightEvent = SelectedEvent->GetTrack()->SearchEvent( FTimingEventSearchParameters(EndTime, Viewport.GetMaxValidTime(), ETimingEventSearchFlags::StopAtFirstMatch, EventFilter)); if (RightEvent.IsValid()) { SelectedEvent = RightEvent; BringIntoView(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime()); OnSelectedTimingEventChanged(); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::SelectUpTimingEvent() { if (SelectedEvent.IsValid() && SelectedEvent->GetDepth() > 0) { const uint32 Depth = SelectedEvent->GetDepth() - 1; const double StartTime = SelectedEvent->GetStartTime(); const double EndTime = SelectedEvent->GetEndTime(); auto EventFilter = [Depth, StartTime, EndTime](double EventStartTime, double EventEndTime, uint32 EventDepth) { return EventDepth == Depth && EventStartTime <= EndTime && EventEndTime >= StartTime; }; const TSharedPtr UpEvent = SelectedEvent->GetTrack()->SearchEvent( FTimingEventSearchParameters(StartTime, EndTime, ETimingEventSearchFlags::StopAtFirstMatch, EventFilter)); if (UpEvent.IsValid()) { SelectedEvent = UpEvent; BringIntoView(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime()); OnSelectedTimingEventChanged(); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::SelectDownTimingEvent() { if (SelectedEvent.IsValid()) { const uint32 Depth = SelectedEvent->GetDepth() + 1; const double StartTime = SelectedEvent->GetStartTime(); const double EndTime = SelectedEvent->GetEndTime(); double LargestDuration = 0.0; auto EventFilter = [Depth, StartTime, EndTime, &LargestDuration](double EventStartTime, double EventEndTime, uint32 EventDepth) { const double Duration = EventEndTime - EventStartTime; return Duration > LargestDuration && EventDepth == Depth && EventStartTime <= EndTime && EventEndTime >= StartTime; }; auto EventMatched = [&LargestDuration](double EventStartTime, double EventEndTime, uint32 EventDepth) { const double Duration = EventEndTime - EventStartTime; LargestDuration = Duration; }; const TSharedPtr DownEvent = SelectedEvent->GetTrack()->SearchEvent( FTimingEventSearchParameters(StartTime, EndTime, ETimingEventSearchFlags::SearchAll, EventFilter, EventMatched)); if (DownEvent.IsValid()) { SelectedEvent = DownEvent; BringIntoView(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime()); OnSelectedTimingEventChanged(); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::FrameSelection() { double StartTime, EndTime; ESelectionType Type = ESelectionType::None; if (LastSelectionType == ESelectionType::TimingEvent) { // Try framing the selected timing event. if (SelectedEvent.IsValid()) { Type = ESelectionType::TimingEvent; } // Next time, try framing the selected time range. LastSelectionType = ESelectionType::TimeRange; } else if (LastSelectionType == ESelectionType::TimeRange) { // Try framing the selected time range. if (SelectionEndTime > SelectionStartTime) { Type = ESelectionType::TimeRange; } // Next time, try framing the selected timing event. LastSelectionType = ESelectionType::TimingEvent; } // If no last selection or last selection is empty... if (LastSelectionType == ESelectionType::None || Type == ESelectionType::None) { // First, try framing the selected timing event... if (SelectedEvent.IsValid()) { Type = ESelectionType::TimingEvent; } else // ...otherwise, try framing the selected time range { Type = ESelectionType::TimeRange; } } if (Type == ESelectionType::TimingEvent) { // Frame the selected event. StartTime = SelectedEvent->GetStartTime(); EndTime = Viewport.RestrictEndTime(SelectedEvent->GetEndTime()); if (EndTime == StartTime) { EndTime += 1.0 / Viewport.GetScaleX(); // +1px } } else { // Frame the selected time range. StartTime = SelectionStartTime; EndTime = Viewport.RestrictEndTime(SelectionEndTime); } if (EndTime > StartTime) { const double Duration = EndTime - StartTime; if (Viewport.ZoomOnTimeInterval(StartTime - Duration * 0.1, Duration * 1.2)) { UpdateHorizontalScrollBar(); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::SetEventFilter(const TSharedPtr InEventFilter) { TimingEventFilter = InEventFilter; Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::ToggleEventFilterByEventType(const uint64 EventType) { if (IsFilterByEventType(EventType)) { SetEventFilter(nullptr); // reset filter } else { TSharedRef NewEventFilter = MakeShared(EventType); SetEventFilter(NewEventFilter); // set new filter } } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimingView::IsFilterByEventType(const uint64 EventType) const { if (TimingEventFilter.IsValid() && TimingEventFilter->Is()) { const FTimingEventFilterByEventType& EventFilterByEventType = TimingEventFilter->As(); return EventFilterByEventType.GetEventType() == EventType; } return false; } //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedRef STimingView::MakeAutoScrollOptionsMenu() { FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, nullptr); MenuBuilder.BeginSection("QuickFilter", LOCTEXT("AutoScrollHeading", "Auto Scroll Options")); { MenuBuilder.AddMenuEntry( LOCTEXT("AutoScrollFrameAligned", "Frame Aligned"), LOCTEXT("AutoScrollFrameAligned_Tooltip", "Align the viewport's center position with the start time of a frame."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollFrameAligned_Execute), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &STimingView::AutoScrollFrameAligned_IsChecked)), NAME_None, EUserInterfaceActionType::ToggleButton); MenuBuilder.AddMenuEntry( LOCTEXT("AutoScrollFrameTypeGame", "Align with Game Frames"), LOCTEXT("AutoScrollFrameTypeGame_Tooltip", "Align the viewport's center position with the start time of a Game frame."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollFrameType_Execute, TraceFrameType_Game), FCanExecuteAction::CreateSP(this, &STimingView::AutoScrollFrameType_CanExecute, TraceFrameType_Game), FIsActionChecked::CreateSP(this, &STimingView::AutoScrollFrameType_IsChecked, TraceFrameType_Game)), NAME_None, EUserInterfaceActionType::RadioButton); MenuBuilder.AddMenuEntry( LOCTEXT("AutoScrollFrameTypeRendering", "Align with Rendering Frames"), LOCTEXT("AutoScrollFrameTypeRendering_Tooltip", "Align the viewport's center position with the start time of a Rendering frame."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollFrameType_Execute, TraceFrameType_Rendering), FCanExecuteAction::CreateSP(this, &STimingView::AutoScrollFrameType_CanExecute, TraceFrameType_Rendering), FIsActionChecked::CreateSP(this, &STimingView::AutoScrollFrameType_IsChecked, TraceFrameType_Rendering)), NAME_None, EUserInterfaceActionType::RadioButton); MenuBuilder.AddMenuSeparator(); MenuBuilder.AddMenuEntry( LOCTEXT("AutoScrollViewportOffset-10", "Viewport Offset: -10%"), LOCTEXT("AutoScrollViewportOffset-10_Tooltip", "Set the viewport offset to -10% (i.e. backward) of the viewport's width.\nAvoids flickering as the end of session will be outside of the viewport."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollViewportOffset_Execute, -0.1), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &STimingView::AutoScrollViewportOffset_IsChecked, -0.1)), NAME_None, EUserInterfaceActionType::RadioButton); MenuBuilder.AddMenuEntry( LOCTEXT("AutoScrollViewportOffset0", "Viewport Offset: 0"), LOCTEXT("AutoScrollViewportOffset0_Tooltip", "Set the viewport offset to 0."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollViewportOffset_Execute, 0.0), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &STimingView::AutoScrollViewportOffset_IsChecked, 0.0)), NAME_None, EUserInterfaceActionType::RadioButton); MenuBuilder.AddMenuEntry( LOCTEXT("AutoScrollViewportOffset+10", "Viewport Offset: +10%"), LOCTEXT("AutoScrollViewportOffset+10_Tooltip", "Set the viewport offset to +10% (i.e. forward) of the viewport's width.\nAllows 10% empty space on the right side of the viewport."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollViewportOffset_Execute, +0.1), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &STimingView::AutoScrollViewportOffset_IsChecked, +0.1)), NAME_None, EUserInterfaceActionType::RadioButton); MenuBuilder.AddMenuSeparator(); MenuBuilder.AddMenuEntry( LOCTEXT("AutoScrollDelay0", "Delay: 0"), LOCTEXT("AutoScrollDelay0_Tooltip", "Set the time delay of the auto-scroll update to 0."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollDelay_Execute, 0.0), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &STimingView::AutoScrollDelay_IsChecked, 0.0)), NAME_None, EUserInterfaceActionType::RadioButton); MenuBuilder.AddMenuEntry( LOCTEXT("AutoScrollDelay300ms", "Delay: 300ms"), LOCTEXT("AutoScrollDelay300ms_Tooltip", "Set the time delay of the auto-scroll update to 300ms."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollDelay_Execute, 0.3), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &STimingView::AutoScrollDelay_IsChecked, 0.3)), NAME_None, EUserInterfaceActionType::RadioButton); MenuBuilder.AddMenuEntry( LOCTEXT("AutoScrollDelay1s", "Delay: 1s"), LOCTEXT("AutoScrollDelay1s_Tooltip", "Set the time delay of the auto-scroll update to 1s."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollDelay_Execute, 1.0), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &STimingView::AutoScrollDelay_IsChecked, 1.0)), NAME_None, EUserInterfaceActionType::RadioButton); MenuBuilder.AddMenuEntry( LOCTEXT("AutoScrollDelay3s", "Delay: 3s"), LOCTEXT("AutoScrollDelay3s_Tooltip", "Set the time delay of the auto-scroll update to 3s."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollDelay_Execute, 3.0), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &STimingView::AutoScrollDelay_IsChecked, 3.0)), NAME_None, EUserInterfaceActionType::RadioButton); } return MenuBuilder.MakeWidget(); } //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedRef STimingView::MakeTracksFilterMenu() { FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, nullptr); CreateAllTracksMenu(MenuBuilder); MenuBuilder.BeginSection("QuickFilter", LOCTEXT("TracksFilterHeading", "Quick Filter")); { // const FTimingViewCommands& Commands = FTimingViewCommands::Get(); // TODO: MenuBuilder.AddMenuEntry(Commands.AutoHideGraphTrack); MenuBuilder.AddMenuEntry( LOCTEXT("ShowGraphTrack", "Graph Track - G"), LOCTEXT("ShowGraphTrack_Tooltip", "Show/hide the Graph track"), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &STimingView::ShowHideGraphTrack_Execute), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &STimingView::ShowHideGraphTrack_IsChecked)), NAME_None, EUserInterfaceActionType::ToggleButton); MenuBuilder.AddMenuSeparator("QuickFilterSeparator"); // TODO: MenuBuilder.AddMenuEntry(Commands.AutoHideEmptyTracks); MenuBuilder.AddMenuEntry( LOCTEXT("AutoHideEmptyTracks", "Auto Hide Empty Tracks - V"), LOCTEXT("AutoHideEmptyTracks_Tooltip", "Auto hide empty tracks (ones without timing events in current viewport)"), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &STimingView::ToggleAutoHideEmptyTracks), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &STimingView::IsAutoHideEmptyTracksEnabled)), NAME_None, EUserInterfaceActionType::ToggleButton); } MenuBuilder.EndSection(); // Let any plugin extend the filter menu. for (Insights::ITimingViewExtender* Extender: GetExtenders()) { Extender->ExtendFilterMenu(*this, MenuBuilder); } return MenuBuilder.MakeWidget(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::CreateAllTracksMenu(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection("AllTracks", LOCTEXT("AllTracksHeading", "All Tracks")); if (TopDockedTracks.Num() > 0) { MenuBuilder.AddSubMenu( LOCTEXT("TopDockedTracks", "Top Docked Tracks"), LOCTEXT("TopDockedTracks_Tooltip", "Show/hide individual top docked tracks"), FNewMenuDelegate::CreateLambda([this](FMenuBuilder& InSubMenuBuilder) { InSubMenuBuilder.AddWidget( SNew(SBox) .MaxDesiredHeight(300.0f) .MinDesiredWidth(300.0f) .MaxDesiredWidth(300.0f) [SNew(STimingViewTrackList, SharedThis(this), ETimingTrackLocation::TopDocked)], FText(), true); })); } if (BottomDockedTracks.Num() > 0) { MenuBuilder.AddSubMenu( LOCTEXT("BottomDockedTracks", "Bottom Docked Tracks"), LOCTEXT("BottomDockedTracks_Tooltip", "Show/hide individual bottom docked tracks"), FNewMenuDelegate::CreateLambda([this](FMenuBuilder& InSubMenuBuilder) { InSubMenuBuilder.AddWidget( SNew(SBox) .MaxDesiredHeight(300.0f) .MinDesiredWidth(300.0f) .MaxDesiredWidth(300.0f) [SNew(STimingViewTrackList, SharedThis(this), ETimingTrackLocation::BottomDocked)], FText(), true); })); } if (ScrollableTracks.Num() > 0) { MenuBuilder.AddSubMenu( LOCTEXT("ScrollableTracks", "Scrollable Tracks"), LOCTEXT("ScrollableTracks_Tooltip", "Show/hide individual scrollable tracks"), FNewMenuDelegate::CreateLambda([this](FMenuBuilder& InSubMenuBuilder) { InSubMenuBuilder.AddWidget( SNew(SBox) .MaxDesiredHeight(300.0f) .MinDesiredWidth(300.0f) .MaxDesiredWidth(300.0f) [SNew(STimingViewTrackList, SharedThis(this), ETimingTrackLocation::Scrollable)], FText(), true); })); } if (ForegroundTracks.Num() > 0) { MenuBuilder.AddSubMenu( LOCTEXT("ForegroundTracks", "Foreground Tracks"), LOCTEXT("ForegroundTracks_Tooltip", "Show/hide individual foreground tracks"), FNewMenuDelegate::CreateLambda([this](FMenuBuilder& InSubMenuBuilder) { InSubMenuBuilder.AddWidget( SNew(SBox) .MaxDesiredHeight(300.0f) .MinDesiredWidth(300.0f) .MaxDesiredWidth(300.0f) [SNew(STimingViewTrackList, SharedThis(this), ETimingTrackLocation::Foreground)], FText(), true); })); } MenuBuilder.EndSection(); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimingView::ShowHideGraphTrack_IsChecked() const { return GraphTrack->IsVisible(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::ShowHideGraphTrack_Execute() { GraphTrack->ToggleVisibility(); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimingView::IsAutoHideEmptyTracksEnabled() const { return (Viewport.GetLayout().TargetMinTimelineH == 0.0f); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::ToggleAutoHideEmptyTracks() { Viewport.ToggleLayoutMinTrackHeight(); for (const TSharedPtr& TrackPtr: ScrollableTracks) { TrackPtr->SetHeight(0.0f); } ScrollAtPosY(0.0f); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimingView::ToggleTrackVisibility_IsChecked(uint64 InTrackId) const { const TSharedPtr* const TrackPtrPtr = AllTracks.Find(InTrackId); if (TrackPtrPtr) { return (*TrackPtrPtr)->IsVisible(); } return false; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::ToggleTrackVisibility_Execute(uint64 InTrackId) { const TSharedPtr* TrackPtrPtr = AllTracks.Find(InTrackId); if (TrackPtrPtr) { (*TrackPtrPtr)->ToggleVisibility(); OnTrackVisibilityChanged(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::OnTrackVisibilityChanged() { if (HoveredTrack.IsValid()) { HoveredTrack.Reset(); OnHoveredTrackChangedDelegate.Broadcast(HoveredTrack); } if (HoveredEvent.IsValid()) { HoveredEvent.Reset(); OnHoveredEventChangedDelegate.Broadcast(HoveredEvent); } if (SelectedTrack.IsValid()) { SelectedTrack.Reset(); OnSelectedTrackChangedDelegate.Broadcast(SelectedTrack); } if (SelectedEvent.IsValid()) { SelectedEvent.Reset(); OnSelectedEventChangedDelegate.Broadcast(SelectedEvent); } Tooltip.SetDesiredOpacity(0.0f); // TODO: ThreadFilterChangedEvent.Broadcast(); FTimingProfilerManager::Get()->OnThreadFilterChanged(); if (SelectionStartTime < SelectionEndTime) { UpdateAggregatedStats(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimingView::PreventThrottling() { bPreventThrottling = true; } //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedPtr STimingView::FindTrack(uint64 InTrackId) { TSharedPtr* TrackPtrPtr = AllTracks.Find(InTrackId); return TrackPtrPtr ? *TrackPtrPtr : nullptr; } //////////////////////////////////////////////////////////////////////////////////////////////////// TArray STimingView::GetExtenders() const { return IModularFeatures::Get().GetModularFeatureImplementations(Insights::TimingViewExtenderFeatureName); } //////////////////////////////////////////////////////////////////////////////////////////////////// #undef ACTIVATE_BENCHMARK #undef LOCTEXT_NAMESPACE