Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Private/PerformanceMonitor.h
2025-05-18 13:04:45 +08:00

213 lines
5.8 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "SlateFwd.h"
#include "Stats/Stats.h"
#include "HAL/IConsoleManager.h"
#include "TickableEditorObject.h"
#include "Scalability.h"
class SWindow;
/**
* Helper class for a calculating a moving average. This works by maintaining an accumulator which is then sampled at regular intervals into the "Samples" array.
*/
struct FMovingAverage
{
/** Constructor from a sample size, and sample rate */
FMovingAverage(const int32 InSampleSize = 0, const float InSampleRateSeconds = 1.f)
: CurrentSampleCount(0)
, CurrentSampleAccumulator(0)
, CurrentSampleStartTime(0)
, SampleRateSeconds(InSampleRateSeconds)
, SampleSize(InSampleSize)
, SampleAverage(0)
, NextSampleIndex(0)
{
Samples.Reserve(SampleSize);
}
/** Check if this data is reliable or not. Returns false if the sampler is not populated with enough data */
inline bool IsReliable() const
{
return SampleSize != 0 && Samples.Num() == SampleSize;
}
/** Reset this sampler to its default (unpopulated) state */
inline void Reset()
{
*this = FMovingAverage(SampleSize, SampleRateSeconds);
}
/** Get the average of all the samples contained in this sampler */
inline float GetAverage() const
{
return SampleAverage;
}
/** Get the current number of available samples */
inline int32 GetSampleCount() const
{
return Samples.Num();
}
/** Allow this sampler to tick the frame time, and potentially save a new sample */
void Tick(double CurrentTime, float Value)
{
if (SampleSize == 0)
{
return;
}
if (CurrentSampleStartTime == 0)
{
CurrentSampleStartTime = CurrentTime;
}
++CurrentSampleCount;
CurrentSampleAccumulator += Value;
if (CurrentTime - CurrentSampleStartTime > SampleRateSeconds)
{
// Limit to a minimum of 5 frames per second
const float AccumulatorAverage = FMath::Max(CurrentSampleAccumulator / CurrentSampleCount, 5.f);
if (Samples.Num() == SampleSize)
{
Samples[NextSampleIndex] = AccumulatorAverage;
}
else
{
Samples.Add(AccumulatorAverage);
}
NextSampleIndex = (NextSampleIndex + 1) % SampleSize;
ensure(NextSampleIndex >= 0 && NextSampleIndex < SampleSize);
// calculate the average
float Sum = 0.0;
for (const auto& Sample : Samples)
{
Sum += Sample;
}
SampleAverage = Sum / Samples.Num();
// reset the accumulator and counter
CurrentSampleAccumulator = 0.0f;
CurrentSampleCount = 0;
CurrentSampleStartTime = CurrentTime;
}
}
/** Return the percentage of samples that fall below the specified threshold */
float PercentageBelowThreshold(const float Threshold) const
{
check(IsReliable());
float NumBelowThreshold = 0;
for (const auto& Sample : Samples)
{
if (Sample < Threshold)
{
++NumBelowThreshold;
}
}
return (NumBelowThreshold / Samples.Num()) * 100.0f;
}
private:
/** The number of samples in the current accumulator */
int32 CurrentSampleCount;
/** The cumulative sum of samples for the current time period */
float CurrentSampleAccumulator;
/** The time at which we started accumulating the current sample */
double CurrentSampleStartTime;
/** The rate at which to store accumulated samples of FPS in seconds */
float SampleRateSeconds;
/** The maximum number of accumulated samples to store */
int32 SampleSize;
/** The actual average stored across all samples */
float SampleAverage;
/** The actual FPS samples */
TArray<float> Samples;
/** The index of the next sample to write to */
int32 NextSampleIndex;
};
/** Notification class for performance warnings. */
class FPerformanceMonitor
: public FTickableEditorObject
{
public:
/** Constructor */
FPerformanceMonitor();
/** Destructor */
~FPerformanceMonitor();
protected:
/** FTickableEditorObject interface */
virtual ETickableTickType GetTickableTickType() const override { return ETickableTickType::Always; }
virtual void Tick(float DeltaTime) override;
virtual TStatId GetStatId() const override { RETURN_QUICK_DECLARE_CYCLE_STAT(FPerformanceMonitor, STATGROUP_Tickables); }
private:
/** Starts the notification. */
void ShowPerformanceWarning(FText MessageText);
/** Gets the quality levels that would be applied with auto-scalability. */
Scalability::FQualityLevels GetAutoScalabilityQualityLevels() const;
/** Returns true if the quality settings match the ones that would be applied with auto scalability. */
bool WillAutoScalabilityHelp() const;
/** Run a benchmark and auto apply scalability settings */
void AutoApplyScalability();
/** Adjusts the performance monitor settings to stop monitoring. */
void CancelPerformanceNotification();
/** Ends the notification. */
void HidePerformanceWarning();
/** Display the scalability dialog. */
void ShowScalabilityDialog();
/** Resets the performance warning data and hides the warning */
void Reset();
/** Update the moving average samplers to match the settings specified in the console variables */
void UpdateMovingAverageSamplers();
/** Moving average data for the fine and coarse-grained moving averages */
FMovingAverage FineMovingAverage, CoarseMovingAverage;
/** Tracks the last time the notification was started, used to avoid spamming. */
double LastEnableTime;
/** The time remaining before the auto scalability settings are automatically applied. */
double NotificationTimeout;
/** The notification window ptr */
TWeakPtr<SNotificationItem> PerformanceWarningNotificationPtr;
/** The Scalability Setting window we may be using */
TWeakPtr<SWindow> ScalabilitySettingsWindowPtr;
/** Whether the notification is allowed to pop up this session */
bool bIsNotificationAllowed;
/** Console variable delegate for checking when the console variables have changed */
FConsoleCommandDelegate CVarDelegate;
FConsoleVariableSinkHandle CVarDelegateHandle;
};