1589 lines
52 KiB
C++
1589 lines
52 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "AutomationBlueprintFunctionLibrary.h"
|
|
#include "HAL/IConsoleManager.h"
|
|
#include "Misc/AutomationTest.h"
|
|
#include "EngineGlobals.h"
|
|
#include "UnrealClient.h"
|
|
#include "Camera/CameraActor.h"
|
|
#include "Camera/PlayerCameraManager.h"
|
|
#include "Engine/Texture.h"
|
|
#include "Engine/GameViewportClient.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
#include "Engine/Engine.h"
|
|
#if WITH_EDITOR
|
|
#include "Editor/EditorEngine.h"
|
|
#include "Editor.h"
|
|
#include "HighResScreenshot.h"
|
|
#include "LevelEditor.h"
|
|
#include "IAssetViewport.h"
|
|
#include "LevelEditorViewport.h"
|
|
#endif
|
|
#include "Tests/AutomationCommon.h"
|
|
#include "Logging/MessageLog.h"
|
|
#include "TakeScreenshotAfterTimeLatentAction.h"
|
|
#include "HighResScreenshot.h"
|
|
#include "Slate/SceneViewport.h"
|
|
#include "Tests/AutomationTestSettings.h"
|
|
#include "Slate/WidgetRenderer.h"
|
|
#include "DelayAction.h"
|
|
#include "Widgets/SViewport.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "ShaderCompiler.h"
|
|
#include "AutomationBlueprintFunctionLibrary.h"
|
|
#include "BufferVisualizationData.h"
|
|
#include "Engine/LocalPlayer.h"
|
|
#include "ContentStreaming.h"
|
|
#include "Stats/StatsData.h"
|
|
#include "HAL/PlatformProperties.h"
|
|
#include "IAutomationControllerModule.h"
|
|
#include "Scalability.h"
|
|
#include "SceneViewExtension.h"
|
|
#include "SceneView.h"
|
|
#include "Engine/GameEngine.h"
|
|
#include "Engine/LevelStreaming.h"
|
|
#include "Templates/UnrealTemplate.h"
|
|
#include "UObject/GCObjectScopeGuard.h"
|
|
#include "Containers/Ticker.h"
|
|
#include "IImageWrapper.h"
|
|
#include "IImageWrapperModule.h"
|
|
#include "ImageWrapperHelper.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Materials/MaterialInterface.h"
|
|
#include "AssetCompilingManager.h"
|
|
#include "DynamicResolutionState.h"
|
|
|
|
#if WITH_EDITOR
|
|
#include "SLevelViewport.h"
|
|
#endif
|
|
#include "FunctionalTestBase.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(AutomationBlueprintFunctionLibrary)
|
|
|
|
|
|
#define LOCTEXT_NAMESPACE "Automation"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(BlueprintAssertion, Error, Error)
|
|
DEFINE_LOG_CATEGORY_STATIC(AutomationFunctionLibrary, Log, Log)
|
|
|
|
static TAutoConsoleVariable<int32> CVarAutomationScreenshotResolutionWidth(
|
|
TEXT("AutomationScreenshotResolutionWidth"),
|
|
0,
|
|
TEXT("The width of automation screenshots."),
|
|
ECVF_Default);
|
|
|
|
static TAutoConsoleVariable<int32> CVarAutomationScreenshotResolutionHeight(
|
|
TEXT("AutomationScreenshotResolutionHeight"),
|
|
0,
|
|
TEXT("The height of automation screenshots."),
|
|
ECVF_Default);
|
|
|
|
|
|
bool UAutomationEditorTask::IsValidTask() const
|
|
{
|
|
return Task.IsValid();
|
|
}
|
|
|
|
void UAutomationEditorTask::BindTask(TUniquePtr<FAutomationTaskStatusBase> inTask)
|
|
{
|
|
Task = MoveTemp(inTask);
|
|
}
|
|
|
|
bool UAutomationEditorTask::IsTaskDone() const
|
|
{
|
|
return IsValidTask() && Task->IsDone();
|
|
}
|
|
|
|
#if WITH_AUTOMATION_TESTS
|
|
|
|
template<typename T>
|
|
FConsoleVariableSwapperTempl<T>::FConsoleVariableSwapperTempl(FString InConsoleVariableName)
|
|
: bModified(false)
|
|
, ConsoleVariableName(InConsoleVariableName)
|
|
{
|
|
}
|
|
|
|
template<typename T>
|
|
void FConsoleVariableSwapperTempl<T>::Set(T Value)
|
|
{
|
|
IConsoleVariable* ConsoleVariable = IConsoleManager::Get().FindConsoleVariable(*ConsoleVariableName);
|
|
if (ensure(ConsoleVariable))
|
|
{
|
|
if (bModified == false)
|
|
{
|
|
bModified = true;
|
|
OriginalValue = ConsoleVariable->GetInt();
|
|
}
|
|
|
|
ConsoleVariable->AsVariable()->SetWithCurrentPriority(Value);
|
|
}
|
|
}
|
|
|
|
template<>
|
|
void FConsoleVariableSwapperTempl<float>::Set(float Value)
|
|
{
|
|
IConsoleVariable* ConsoleVariable = IConsoleManager::Get().FindConsoleVariable(*ConsoleVariableName);
|
|
if (ensure(ConsoleVariable))
|
|
{
|
|
if (bModified == false)
|
|
{
|
|
bModified = true;
|
|
OriginalValue = ConsoleVariable->GetFloat();
|
|
}
|
|
|
|
// I need these overrides to superseded anything the user does while taking the shot.
|
|
ConsoleVariable->AsVariable()->SetWithCurrentPriority(Value);
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
void FConsoleVariableSwapperTempl<T>::Restore()
|
|
{
|
|
if (bModified)
|
|
{
|
|
IConsoleVariable* ConsoleVariable = IConsoleManager::Get().FindConsoleVariable(*ConsoleVariableName);
|
|
if (ensure(ConsoleVariable))
|
|
{
|
|
// First we stomp the current with the original, then restore the original flags
|
|
// so that code continues to treat it using whatever source it was from originally, code, cmdline..etc.
|
|
ConsoleVariable->AsVariable()->SetWithCurrentPriority(OriginalValue);
|
|
}
|
|
|
|
bModified = false;
|
|
}
|
|
}
|
|
|
|
class FAutomationViewExtension : public FWorldSceneViewExtension
|
|
{
|
|
public:
|
|
FAutomationViewExtension(const FAutoRegister& AutoRegister, UWorld* InWorld, FAutomationScreenshotOptions& InOptions, float InCurrentTimeToSimulate)
|
|
: FWorldSceneViewExtension(AutoRegister, InWorld)
|
|
, Options(InOptions)
|
|
{
|
|
}
|
|
|
|
/** ISceneViewExtension interface */
|
|
virtual void SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView)
|
|
{
|
|
//if (Options.VisualizeBuffer != NAME_None)
|
|
//{
|
|
// InViewFamily.ViewMode = VMI_VisualizeBuffer;
|
|
// InViewFamily.EngineShowFlags.SetVisualizeBuffer(true);
|
|
// InViewFamily.EngineShowFlags.SetTonemapper(false);
|
|
|
|
// if (GetBufferVisualizationData().GetMaterial(Options.VisualizeBuffer) == NULL)
|
|
// {
|
|
// InView.CurrentBufferVisualizationMode = Options.VisualizeBuffer;
|
|
// }
|
|
//}
|
|
}
|
|
|
|
virtual void SetupViewFamily(FSceneViewFamily& InViewFamily) override
|
|
{
|
|
if (UAutomationViewSettings* ViewSettings = Options.ViewSettings)
|
|
{
|
|
// Turn off common show flags for noisy sources of rendering.
|
|
FEngineShowFlags& ShowFlags = InViewFamily.EngineShowFlags;
|
|
ShowFlags.SetAntiAliasing(ViewSettings->AntiAliasing);
|
|
ShowFlags.SetMotionBlur(ViewSettings->MotionBlur);
|
|
ShowFlags.SetTemporalAA(ViewSettings->TemporalAA);
|
|
ShowFlags.SetScreenSpaceReflections(ViewSettings->ScreenSpaceReflections);
|
|
ShowFlags.SetScreenSpaceAO(ViewSettings->ScreenSpaceAO);
|
|
ShowFlags.SetDistanceFieldAO(ViewSettings->DistanceFieldAO);
|
|
ShowFlags.SetContactShadows(ViewSettings->ContactShadows);
|
|
ShowFlags.SetEyeAdaptation(ViewSettings->EyeAdaptation);
|
|
ShowFlags.SetBloom(ViewSettings->Bloom);
|
|
}
|
|
|
|
if (Options.bOverride_OverrideTimeTo)
|
|
{
|
|
// Turn off time the ultimate source of noise.
|
|
InViewFamily.Time = FGameTime::CreateUndilated(Options.OverrideTimeTo, 0.0f);
|
|
}
|
|
|
|
if (Options.bDisableNoisyRenderingFeatures)
|
|
{
|
|
//// Turn off common show flags for noisy sources of rendering.
|
|
//InViewFamily.EngineShowFlags.SetAntiAliasing(false);
|
|
//InViewFamily.EngineShowFlags.SetMotionBlur(false);
|
|
//InViewFamily.EngineShowFlags.SetTemporalAA(false);
|
|
//InViewFamily.EngineShowFlags.SetScreenSpaceReflections(false);
|
|
////InViewFamily.EngineShowFlags.SetScreenSpaceAO(false);
|
|
////InViewFamily.EngineShowFlags.SetDistanceFieldAO(false);
|
|
//InViewFamily.EngineShowFlags.SetContactShadows(false);
|
|
//InViewFamily.EngineShowFlags.SetEyeAdaptation(false);
|
|
|
|
//TODO Auto Exposure?
|
|
//TODO EyeAdaptation Gamma?
|
|
|
|
// Disable screen percentage.
|
|
//InViewFamily.EngineShowFlags.SetScreenPercentage(false);
|
|
}
|
|
|
|
if (Options.bDisableTonemapping)
|
|
{
|
|
//InViewFamily.EngineShowFlags.SetEyeAdaptation(false);
|
|
//InViewFamily.EngineShowFlags.SetTonemapper(false);
|
|
}
|
|
}
|
|
|
|
/** We always want to go last. */
|
|
virtual int32 GetPriority() const override { return MIN_int32; }
|
|
|
|
private:
|
|
FAutomationScreenshotOptions Options;
|
|
};
|
|
|
|
FAutomationTestScreenshotEnvSetup::FAutomationTestScreenshotEnvSetup()
|
|
: DefaultFeature_AntiAliasing(TEXT("r.AntiAliasingMethod"))
|
|
, DefaultFeature_AutoExposure(TEXT("r.DefaultFeature.AutoExposure"))
|
|
, DefaultFeature_MotionBlur(TEXT("r.DefaultFeature.MotionBlur"))
|
|
, MotionBlurQuality(TEXT("r.MotionBlurQuality"))
|
|
, ScreenSpaceReflectionQuality(TEXT("r.SSR.Quality"))
|
|
, EyeAdaptationQuality(TEXT("r.EyeAdaptationQuality"))
|
|
, ContactShadows(TEXT("r.ContactShadows"))
|
|
, TonemapperGamma(TEXT("r.TonemapperGamma"))
|
|
, TonemapperSharpen(TEXT("r.Tonemapper.Sharpen"))
|
|
, ScreenPercentage(TEXT("r.ScreenPercentage"))
|
|
, DynamicResTestScreenPercentage(TEXT("r.DynamicRes.TestScreenPercentage"))
|
|
, DynamicResOperationMode(TEXT("r.DynamicRes.OperationMode"))
|
|
, SecondaryScreenPercentage(TEXT("r.SecondaryScreenPercentage.GameViewport"))
|
|
{
|
|
}
|
|
|
|
FAutomationTestScreenshotEnvSetup::~FAutomationTestScreenshotEnvSetup()
|
|
{
|
|
}
|
|
|
|
void FAutomationTestScreenshotEnvSetup::Setup(UWorld* InWorld, FAutomationScreenshotOptions& InOutOptions)
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
WorldPtr = InWorld;
|
|
|
|
if (InOutOptions.bDisableNoisyRenderingFeatures)
|
|
{
|
|
DefaultFeature_AntiAliasing.Set(0);
|
|
DefaultFeature_AutoExposure.Set(0);
|
|
DefaultFeature_MotionBlur.Set(0);
|
|
MotionBlurQuality.Set(0);
|
|
ScreenSpaceReflectionQuality.Set(0);
|
|
ContactShadows.Set(0);
|
|
EyeAdaptationQuality.Set(0);
|
|
TonemapperGamma.Set(2.2f);
|
|
}
|
|
else if (InOutOptions.bDisableTonemapping)
|
|
{
|
|
EyeAdaptationQuality.Set(0);
|
|
TonemapperGamma.Set(2.2f);
|
|
}
|
|
|
|
// Forces ScreenPercentage=100
|
|
{
|
|
// Completely disable dynamic resolution
|
|
{
|
|
DynamicResTestScreenPercentage.Set(0);
|
|
DynamicResOperationMode.Set(0);
|
|
|
|
// Dynamic resolution status change is only taking effect at next dyn res frame.
|
|
GEngine->EmitDynamicResolutionEvent(EDynamicResolutionStateEvent::EndFrame);
|
|
GEngine->EmitDynamicResolutionEvent(EDynamicResolutionStateEvent::BeginFrame);
|
|
}
|
|
ScreenPercentage.Set(100.f);
|
|
}
|
|
|
|
// Ignore High-DPI settings
|
|
SecondaryScreenPercentage.Set(100.f);
|
|
|
|
InOutOptions.SetToleranceAmounts(InOutOptions.Tolerance);
|
|
|
|
const float InCurrentTimeToSimulate = 0.0f;
|
|
AutomationViewExtension = FSceneViewExtensions::NewExtension<FAutomationViewExtension>(InWorld, InOutOptions, InCurrentTimeToSimulate);
|
|
|
|
// TODO - I don't like needing to set this here. Because the gameviewport uses a console variable, it wins.
|
|
if (UGameViewportClient* ViewportClient = AutomationCommon::GetAnyGameViewportClient())
|
|
{
|
|
static IConsoleVariable* ICVar = IConsoleManager::Get().FindConsoleVariable(FBufferVisualizationData::GetVisualizationTargetConsoleCommandName());
|
|
if (ICVar)
|
|
{
|
|
if (ViewportClient->GetEngineShowFlags())
|
|
{
|
|
ViewportClient->GetEngineShowFlags()->SetVisualizeBuffer(InOutOptions.VisualizeBuffer == NAME_None ? false : true);
|
|
ViewportClient->GetEngineShowFlags()->SetTonemapper(InOutOptions.VisualizeBuffer == NAME_None ? true : false);
|
|
ICVar->Set(*InOutOptions.VisualizeBuffer.ToString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FAutomationTestScreenshotEnvSetup::Restore()
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
DefaultFeature_AntiAliasing.Restore();
|
|
DefaultFeature_AutoExposure.Restore();
|
|
DefaultFeature_MotionBlur.Restore();
|
|
MotionBlurQuality.Restore();
|
|
ScreenSpaceReflectionQuality.Restore();
|
|
EyeAdaptationQuality.Restore();
|
|
ContactShadows.Restore();
|
|
TonemapperGamma.Restore();
|
|
//TonemapperSharpen.Restore();
|
|
ScreenPercentage.Restore();
|
|
DynamicResOperationMode.Restore();
|
|
DynamicResTestScreenPercentage.Restore();
|
|
SecondaryScreenPercentage.Restore();
|
|
|
|
AutomationViewExtension.Reset();
|
|
|
|
if (UGameViewportClient* ViewportClient = AutomationCommon::GetAnyGameViewportClient())
|
|
{
|
|
static IConsoleVariable* ICVar = IConsoleManager::Get().FindConsoleVariable(FBufferVisualizationData::GetVisualizationTargetConsoleCommandName());
|
|
if (ICVar)
|
|
{
|
|
if (ViewportClient->GetEngineShowFlags())
|
|
{
|
|
ViewportClient->GetEngineShowFlags()->SetVisualizeBuffer(false);
|
|
ViewportClient->GetEngineShowFlags()->SetTonemapper(true);
|
|
ICVar->Set(TEXT(""));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class FAutomationScreenshotTaker
|
|
{
|
|
public:
|
|
FAutomationScreenshotTaker(UWorld* InWorld, const FString& InScreenShotName, const FString& InNotes, FAutomationScreenshotOptions InOptions)
|
|
: World(InWorld)
|
|
, ScreenShotName(InScreenShotName)
|
|
, Notes(InNotes)
|
|
, Options(InOptions)
|
|
, bNeedsViewportSizeRestore(false)
|
|
, bDeleteQueued(false)
|
|
{
|
|
EnvSetup.Setup(InWorld, Options);
|
|
|
|
UGameViewportClient* GameViewportClient = AutomationCommon::GetAnyGameViewportClient();
|
|
if (!FPlatformProperties::HasFixedResolution())
|
|
{
|
|
FSceneViewport* GameViewport = GameViewportClient ? GameViewportClient->GetGameViewport() : nullptr;
|
|
if (GameViewport)
|
|
{
|
|
#if WITH_EDITOR
|
|
// In the editor we can only attempt to re-size standalone viewports
|
|
UEditorEngine* EditorEngine = Cast<UEditorEngine>(GEngine);
|
|
|
|
const bool bIsPIEViewport = GameViewport->IsPlayInEditorViewport();
|
|
const bool bIsNewViewport = GameViewportClient->GetWorld() && EditorEngine && EditorEngine->WorldIsPIEInNewViewport(GameViewportClient->GetWorld());
|
|
|
|
if (!bIsPIEViewport || bIsNewViewport)
|
|
#endif
|
|
{
|
|
ViewportRestoreSize = GameViewport->GetSize();
|
|
FIntPoint ScreenshotViewportSize = UAutomationBlueprintFunctionLibrary::GetAutomationScreenshotSize(InOptions);
|
|
GameViewport->SetViewportSize(ScreenshotViewportSize.X, ScreenshotViewportSize.Y);
|
|
bNeedsViewportSizeRestore = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
FlushRenderingCommands();
|
|
|
|
GameViewportClient->OnScreenshotCaptured().AddRaw(this, &FAutomationScreenshotTaker::GrabScreenShot);
|
|
FWorldDelegates::LevelRemovedFromWorld.AddRaw(this, &FAutomationScreenshotTaker::WorldDestroyed);
|
|
FScreenshotRequest::OnScreenshotRequestProcessed().AddRaw(this, &FAutomationScreenshotTaker::OnScreenshotProcessed);
|
|
}
|
|
|
|
virtual ~FAutomationScreenshotTaker()
|
|
{
|
|
FAutomationTestFramework::Get().OnScreenshotCompared.RemoveAll(this);
|
|
FScreenshotRequest::OnScreenshotRequestProcessed().RemoveAll(this);
|
|
|
|
UGameViewportClient* GameViewportClient = AutomationCommon::GetAnyGameViewportClient();
|
|
if (GameViewportClient)
|
|
{
|
|
// remove before we restore the viewport's size - a resize can trigger a redraw, which would trigger OnScreenshotCaptured() again (endless loop)
|
|
GameViewportClient->OnScreenshotCaptured().RemoveAll(this);
|
|
}
|
|
|
|
FWorldDelegates::LevelRemovedFromWorld.RemoveAll(this);
|
|
|
|
if (!FPlatformProperties::HasFixedResolution() && bNeedsViewportSizeRestore)
|
|
{
|
|
if (GameViewportClient)
|
|
{
|
|
FSceneViewport* GameViewport = GameViewportClient->GetGameViewport();
|
|
GameViewport->SetViewportSize(ViewportRestoreSize.X, ViewportRestoreSize.Y);
|
|
}
|
|
}
|
|
|
|
EnvSetup.Restore();
|
|
|
|
FAutomationTestFramework::Get().NotifyScreenshotTakenAndCompared();
|
|
}
|
|
|
|
void DeleteSelfNextFrame()
|
|
{
|
|
if (!bDeleteQueued)
|
|
{
|
|
FTSTicker::GetCoreTicker().AddTicker(TEXT("ScreenshotCleanup"), 0.1, [this](float) {
|
|
delete this;
|
|
return false;
|
|
});
|
|
bDeleteQueued = true;
|
|
}
|
|
}
|
|
|
|
void GrabScreenShot(int32 InSizeX, int32 InSizeY, const TArray<FColor>& InImageData)
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
if (World.IsValid())
|
|
{
|
|
FAutomationScreenshotData Data = UAutomationBlueprintFunctionLibrary::BuildScreenshotData(World.Get(), ScreenShotName, InSizeX, InSizeY);
|
|
|
|
// Copy the relevant data into the metadata for the screenshot.
|
|
Data.bHasComparisonRules = true;
|
|
Data.ToleranceRed = Options.ToleranceAmount.Red;
|
|
Data.ToleranceGreen = Options.ToleranceAmount.Green;
|
|
Data.ToleranceBlue = Options.ToleranceAmount.Blue;
|
|
Data.ToleranceAlpha = Options.ToleranceAmount.Alpha;
|
|
Data.ToleranceMinBrightness = Options.ToleranceAmount.MinBrightness;
|
|
Data.ToleranceMaxBrightness = Options.ToleranceAmount.MaxBrightness;
|
|
Data.bIgnoreAntiAliasing = Options.bIgnoreAntiAliasing;
|
|
Data.bIgnoreColors = Options.bIgnoreColors;
|
|
Data.MaximumLocalError = Options.MaximumLocalError;
|
|
Data.MaximumGlobalError = Options.MaximumGlobalError;
|
|
|
|
// Record any user notes that were made to accompany this shot.
|
|
Data.Notes = Notes;
|
|
|
|
bool bAttemptToCompareShot = FAutomationTestFramework::Get().OnScreenshotCaptured().ExecuteIfBound(InImageData, Data);
|
|
|
|
UE_LOG(AutomationFunctionLibrary, Log, TEXT("Screenshot captured as %s"), *Data.ScreenshotPath);
|
|
|
|
if (GIsAutomationTesting)
|
|
{
|
|
FAutomationTestFramework::Get().OnScreenshotCompared.AddRaw(this, &FAutomationScreenshotTaker::OnComparisonComplete);
|
|
FScreenshotRequest::OnScreenshotRequestProcessed().RemoveAll(this);
|
|
return;
|
|
}
|
|
}
|
|
|
|
DeleteSelfNextFrame();
|
|
}
|
|
|
|
void OnScreenshotProcessed()
|
|
{
|
|
UE_LOG(AutomationFunctionLibrary, Log, TEXT("Screenshot processed, but not compared."));
|
|
|
|
DeleteSelfNextFrame();
|
|
}
|
|
|
|
void OnComparisonComplete(const FAutomationScreenshotCompareResults& CompareResults)
|
|
{
|
|
FAutomationTestFramework::Get().OnScreenshotCompared.RemoveAll(this);
|
|
|
|
if (FAutomationTestBase* CurrentTest = FAutomationTestFramework::Get().GetCurrentTest())
|
|
{
|
|
CurrentTest->AddEvent(CompareResults.ToAutomationEvent());
|
|
}
|
|
|
|
DeleteSelfNextFrame();
|
|
}
|
|
|
|
void WorldDestroyed(ULevel* InLevel, UWorld* InWorld)
|
|
{
|
|
// If the InLevel is null, it's a signal that the entire world is about to disappear, so
|
|
// go ahead and remove this widget from the viewport, it could be holding onto too many
|
|
// dangerous actor references that won't carry over into the next world.
|
|
if (InLevel == nullptr && InWorld == World.Get())
|
|
{
|
|
// we don't delete directly because of the risk of conflicting with an already in flight
|
|
// request to delete ourselves
|
|
World.Reset();
|
|
DeleteSelfNextFrame();
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
TWeakObjectPtr<UWorld> World;
|
|
|
|
FString Context;
|
|
FString ScreenShotName;
|
|
FString Notes;
|
|
FAutomationScreenshotOptions Options;
|
|
|
|
FAutomationTestScreenshotEnvSetup EnvSetup;
|
|
FIntPoint ViewportRestoreSize;
|
|
bool bNeedsViewportSizeRestore;
|
|
bool bDeleteQueued;
|
|
};
|
|
|
|
class FAutomationHighResScreenshotGrabber
|
|
{
|
|
public:
|
|
FAutomationHighResScreenshotGrabber(const FString& InContext, const FString& InScreenShotName, const FString& InNotes, FAutomationScreenshotOptions InOptions)
|
|
: Context(InContext)
|
|
, ScreenShotName(InScreenShotName)
|
|
, Notes(InNotes)
|
|
, Options(InOptions)
|
|
{
|
|
FScreenshotRequest::OnScreenshotCaptured().AddRaw(this, &FAutomationHighResScreenshotGrabber::GrabScreenShot);
|
|
FWorldDelegates::LevelRemovedFromWorld.AddRaw(this, &FAutomationHighResScreenshotGrabber::WorldDestroyed);
|
|
}
|
|
|
|
virtual ~FAutomationHighResScreenshotGrabber()
|
|
{
|
|
FAutomationTestFramework::Get().OnScreenshotCompared.RemoveAll(this);
|
|
FScreenshotRequest::OnScreenshotCaptured().RemoveAll(this);
|
|
|
|
FWorldDelegates::LevelRemovedFromWorld.RemoveAll(this);
|
|
|
|
FAutomationTestFramework::Get().NotifyScreenshotTakenAndCompared();
|
|
}
|
|
|
|
void GrabScreenShot(int32 InSizeX, int32 InSizeY, const TArray<FColor>& InImageData)
|
|
{
|
|
FScreenshotRequest::OnScreenshotCaptured().RemoveAll(this);
|
|
|
|
FAutomationScreenshotData Data = UAutomationBlueprintFunctionLibrary::BuildScreenshotData(Context, ScreenShotName, InSizeX, InSizeY);
|
|
|
|
// Copy the relevant data into the metadata for the screenshot.
|
|
Data.bHasComparisonRules = true;
|
|
Data.ToleranceRed = Options.ToleranceAmount.Red;
|
|
Data.ToleranceGreen = Options.ToleranceAmount.Green;
|
|
Data.ToleranceBlue = Options.ToleranceAmount.Blue;
|
|
Data.ToleranceAlpha = Options.ToleranceAmount.Alpha;
|
|
Data.ToleranceMinBrightness = Options.ToleranceAmount.MinBrightness;
|
|
Data.ToleranceMaxBrightness = Options.ToleranceAmount.MaxBrightness;
|
|
Data.bIgnoreAntiAliasing = Options.bIgnoreAntiAliasing;
|
|
Data.bIgnoreColors = Options.bIgnoreColors;
|
|
Data.MaximumLocalError = Options.MaximumLocalError;
|
|
Data.MaximumGlobalError = Options.MaximumGlobalError;
|
|
|
|
// Record any user notes that were made to accompany this shot.
|
|
Data.Notes = Notes;
|
|
|
|
bool bAttemptToCompareShot = FAutomationTestFramework::Get().OnScreenshotCaptured().ExecuteIfBound(InImageData, Data);
|
|
|
|
UE_LOG(AutomationFunctionLibrary, Log, TEXT("Screenshot captured as %s"), *Data.ScreenshotPath);
|
|
|
|
FAutomationTestFramework::Get().OnScreenshotCompared.AddRaw(this, &FAutomationHighResScreenshotGrabber::OnComparisonComplete);
|
|
}
|
|
|
|
void OnComparisonComplete(const FAutomationScreenshotCompareResults& CompareResults)
|
|
{
|
|
FAutomationTestFramework::Get().OnScreenshotCompared.RemoveAll(this);
|
|
|
|
if (FAutomationTestBase* CurrentTest = FAutomationTestFramework::Get().GetCurrentTest())
|
|
{
|
|
CurrentTest->AddEvent(CompareResults.ToAutomationEvent());
|
|
}
|
|
|
|
delete this;
|
|
}
|
|
|
|
void WorldDestroyed(ULevel* InLevel, UWorld* InWorld)
|
|
{
|
|
// If the InLevel is null, it's a signal that the entire world is about to disappear, so
|
|
// go ahead and remove this widget from the viewport, it could be holding onto too many
|
|
// dangerous actor references that won't carry over into the next world.
|
|
if (InLevel == nullptr)
|
|
{
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
private:
|
|
FString Context;
|
|
FString ScreenShotName;
|
|
FString Notes;
|
|
FAutomationScreenshotOptions Options;
|
|
};
|
|
|
|
#endif // WITH_AUTOMATION_TESTS
|
|
|
|
class FScreenshotTakenState : public FAutomationTaskStatusBase
|
|
{
|
|
public:
|
|
FScreenshotTakenState(bool InNeedGameViewToggle = true, bool InNeedCameraChange = true)
|
|
: NeedGameViewToggle(InNeedGameViewToggle)
|
|
, NeedCameraChange(InNeedCameraChange)
|
|
{
|
|
if (GIsAutomationTesting)
|
|
{
|
|
// When Automation test are running we hook to the FAutomationTestFramework::OnComparisonComplete instead of the
|
|
// FScreenshotRequest::OnScreenshotRequestProcessed, because with HighResScreenshot, FScreenshotRequest::OnScreenshotRequestProcessed
|
|
// is fired before comparison is completed.
|
|
FAutomationTestFramework::Get().OnScreenshotCompared.AddRaw(this, &FScreenshotTakenState::OnComparisonComplete);
|
|
}
|
|
else
|
|
{
|
|
FScreenshotRequest::OnScreenshotRequestProcessed().AddRaw(this, &FScreenshotTakenState::SetDone);
|
|
}
|
|
};
|
|
|
|
virtual ~FScreenshotTakenState()
|
|
{
|
|
#if WITH_AUTOMATION_TESTS
|
|
FAutomationTestFramework::Get().OnScreenshotCompared.RemoveAll(this);
|
|
#endif
|
|
if (!Done)
|
|
{
|
|
FScreenshotRequest::OnScreenshotRequestProcessed().RemoveAll(this);
|
|
UnlockViewport();
|
|
}
|
|
};
|
|
|
|
virtual void SetDone() override
|
|
{
|
|
FScreenshotRequest::OnScreenshotRequestProcessed().RemoveAll(this);
|
|
|
|
UnlockViewport();
|
|
|
|
Done = true;
|
|
};
|
|
|
|
void OnComparisonComplete(const FAutomationScreenshotCompareResults& CompareResults)
|
|
{
|
|
FAutomationTestFramework::Get().OnScreenshotCompared.RemoveAll(this);
|
|
SetDone();
|
|
};
|
|
|
|
void UnlockViewport()
|
|
{
|
|
#if WITH_EDITOR
|
|
if (FModuleManager::Get().IsModuleLoaded("LevelEditor"))
|
|
{
|
|
FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor");
|
|
SLevelViewport* LevelViewport = LevelEditor.GetFirstActiveLevelViewport().Get();
|
|
if (LevelViewport)
|
|
{
|
|
if (NeedGameViewToggle)
|
|
{
|
|
if (LevelViewport->IsInGameView())
|
|
{
|
|
LevelViewport->ToggleGameView();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(AutomationFunctionLibrary, Verbose, TEXT("Expected to be able to toggle off the Game View mode after the screenshot was taken, but the Viewport was already no longer in that mode or it is not a Perspective."));
|
|
}
|
|
}
|
|
if (NeedCameraChange)
|
|
{
|
|
FLevelEditorViewportClient& LevelViewportClient = LevelViewport->GetLevelViewportClient();
|
|
if (LevelViewportClient.IsAnyActorLocked())
|
|
{
|
|
LevelViewportClient.SetActorLock(nullptr);
|
|
LevelViewportClient.bDisableInput = false;
|
|
LevelViewportClient.bEnableFading = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
};
|
|
|
|
private:
|
|
bool NeedGameViewToggle;
|
|
bool NeedCameraChange;
|
|
|
|
};
|
|
|
|
UAutomationBlueprintFunctionLibrary::UAutomationBlueprintFunctionLibrary(const class FObjectInitializer& Initializer)
|
|
: Super(Initializer)
|
|
{
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::FinishLoadingBeforeScreenshot()
|
|
{
|
|
FlushAsyncLoading();
|
|
|
|
UWorld* CurrentWorld{ nullptr };
|
|
// Make sure we finish all level streaming
|
|
if (UGameEngine* GameEngine = Cast<UGameEngine>(GEngine))
|
|
{
|
|
if (UWorld* GameWorld = GameEngine->GetGameWorld())
|
|
{
|
|
CurrentWorld = GameWorld;
|
|
GameWorld->FlushLevelStreaming(EFlushLevelStreamingType::Full);
|
|
}
|
|
}
|
|
|
|
// Finish compiling the shaders if the platform doesn't require cooked data.
|
|
if (!FPlatformProperties::RequiresCookedData())
|
|
{
|
|
UMaterialInterface::SubmitRemainingJobsForWorld(CurrentWorld);
|
|
FAssetCompilingManager::Get().FinishAllCompilation();
|
|
IAutomationControllerModule* AutomationControllerModule = FModuleManager::GetModulePtr<IAutomationControllerModule>("AutomationController");
|
|
if (AutomationControllerModule != nullptr)
|
|
{
|
|
AutomationControllerModule->GetAutomationController()->ResetAutomationTestTimeout(TEXT("shader compilation"));
|
|
}
|
|
}
|
|
|
|
// Force all mip maps to load before taking the screenshot.
|
|
UTexture::ForceUpdateTextureStreaming();
|
|
|
|
IStreamingManager::Get().StreamAllResources(0.0f);
|
|
}
|
|
|
|
FIntPoint UAutomationBlueprintFunctionLibrary::GetAutomationScreenshotSize(const FAutomationScreenshotOptions& Options)
|
|
{
|
|
// Fallback resolution if all else fails for screenshots.
|
|
uint32 ResolutionX = 1280;
|
|
uint32 ResolutionY = 720;
|
|
|
|
// First get the default set for the project.
|
|
UAutomationTestSettings const* AutomationTestSettings = GetDefault<UAutomationTestSettings>();
|
|
if (AutomationTestSettings->DefaultScreenshotResolution.GetMin() > 0)
|
|
{
|
|
ResolutionX = (uint32)AutomationTestSettings->DefaultScreenshotResolution.X;
|
|
ResolutionY = (uint32)AutomationTestSettings->DefaultScreenshotResolution.Y;
|
|
}
|
|
|
|
// If there's an override resolution, use that instead.
|
|
if (Options.Resolution.GetMin() > 0)
|
|
{
|
|
ResolutionX = (uint32)Options.Resolution.X;
|
|
ResolutionY = (uint32)Options.Resolution.Y;
|
|
}
|
|
else
|
|
{
|
|
// Failing to find an override, look for a platform override that may have been provided through the
|
|
// device profiles setup, to configure the CVars for controlling the automation screenshot size.
|
|
int32 OverrideWidth = CVarAutomationScreenshotResolutionWidth.GetValueOnGameThread();
|
|
int32 OverrideHeight = CVarAutomationScreenshotResolutionHeight.GetValueOnGameThread();
|
|
|
|
if (OverrideWidth > 0)
|
|
{
|
|
ResolutionX = (uint32)OverrideWidth;
|
|
}
|
|
|
|
if (OverrideHeight > 0)
|
|
{
|
|
ResolutionY = (uint32)OverrideHeight;
|
|
}
|
|
}
|
|
|
|
return FIntPoint(ResolutionX, ResolutionY);
|
|
}
|
|
|
|
FAutomationScreenshotData UAutomationBlueprintFunctionLibrary::BuildScreenshotData(const FString& MapOrContext, const FString& ScreenShotName, int32 Width, int32 Height)
|
|
{
|
|
FString TestName = TEXT("");
|
|
if (FAutomationTestFramework::Get().GetCurrentTest())
|
|
{
|
|
TestName = FAutomationTestFramework::Get().GetCurrentTest()->GetTestFullName();
|
|
}
|
|
|
|
#if WITH_AUTOMATION_TESTS
|
|
FAutomationScreenshotData Data = AutomationCommon::BuildScreenshotData(MapOrContext, TestName, ScreenShotName, Width, Height);
|
|
return Data;
|
|
#else
|
|
return FAutomationScreenshotData();
|
|
#endif
|
|
}
|
|
|
|
FAutomationScreenshotData UAutomationBlueprintFunctionLibrary::BuildScreenshotData(UWorld* InWorld, const FString& ScreenShotName, int32 Width, int32 Height)
|
|
{
|
|
return BuildScreenshotData(AutomationCommon::GetWorldContext(InWorld), ScreenShotName, Width, Height);
|
|
}
|
|
|
|
bool UAutomationBlueprintFunctionLibrary::TakeAutomationScreenshotInternal(UObject* WorldContextObject, const FString& ScreenShotName, const FString& Notes, FAutomationScreenshotOptions Options)
|
|
{
|
|
UAutomationBlueprintFunctionLibrary::FinishLoadingBeforeScreenshot();
|
|
|
|
#if WITH_AUTOMATION_TESTS
|
|
FAutomationScreenshotTaker* TempObject = new FAutomationScreenshotTaker(WorldContextObject ? WorldContextObject->GetWorld() : nullptr, ScreenShotName, Notes, Options);
|
|
#endif
|
|
|
|
FScreenshotRequest::RequestScreenshot(false);
|
|
return true; //-V773
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::TakeAutomationScreenshot(UObject* WorldContextObject, FLatentActionInfo LatentInfo, const FString& InScreenShotName, const FString& Notes, const FAutomationScreenshotOptions& Options)
|
|
{
|
|
if ( GIsAutomationTesting )
|
|
{
|
|
FString ScreenShotName = InScreenShotName;
|
|
if ( ScreenShotName.IsEmpty() )
|
|
{
|
|
ScreenShotName = TEXT("Undefined");
|
|
UE_LOG(AutomationFunctionLibrary, Warning, TEXT("Screenshot name is empty. Default name will be used."));
|
|
}
|
|
if ( UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull) )
|
|
{
|
|
FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
|
|
if ( LatentActionManager.FindExistingAction<FTakeScreenshotAfterTimeLatentAction>(LatentInfo.CallbackTarget, LatentInfo.UUID) == nullptr )
|
|
{
|
|
LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, new FTakeScreenshotAfterTimeLatentAction(LatentInfo, ScreenShotName, Notes, Options));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(AutomationFunctionLibrary, Log, TEXT("Screenshot not captured - screenshots are only taken during automation tests"));
|
|
}
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::TakeAutomationScreenshotAtCamera(UObject* WorldContextObject, FLatentActionInfo LatentInfo, ACameraActor* Camera, const FString& NameOverride, const FString& Notes, const FAutomationScreenshotOptions& Options)
|
|
{
|
|
if ( Camera == nullptr )
|
|
{
|
|
FMessageLog("PIE").Error(LOCTEXT("CameraRequired", "A camera is required to TakeAutomationScreenshotAtCamera"));
|
|
return;
|
|
}
|
|
|
|
UGameViewportClient* GameViewportClient = AutomationCommon::GetAnyGameViewportClient();
|
|
if (GameViewportClient == nullptr)
|
|
{
|
|
FMessageLog("PIE").Error(LOCTEXT("GameViewportRequired", "No game viewport found in World to TakeAutomationScreenshotAtCamera. Use a delay or change Net mode to Standalone."));
|
|
return;
|
|
}
|
|
|
|
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GameViewportClient->GetWorld(), 0);
|
|
if ( PlayerController == nullptr )
|
|
{
|
|
FMessageLog("PIE").Error(LOCTEXT("PlayerRequired", "A player controller is required to TakeAutomationScreenshotAtCamera"));
|
|
return;
|
|
}
|
|
|
|
// Move the player, then queue up a screenshot.
|
|
// We need to delay before the screenshot so that the motion blur has time to stop.
|
|
PlayerController->bAutoManageActiveCameraTarget = false;
|
|
PlayerController->SetViewTarget(Camera, FViewTargetTransitionParams());
|
|
FString ScreenshotName = Camera->GetName();
|
|
|
|
if ( !NameOverride.IsEmpty() )
|
|
{
|
|
ScreenshotName = NameOverride;
|
|
}
|
|
|
|
if ( UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull) )
|
|
{
|
|
ScreenshotName = FString::Printf(TEXT("%s_%s"), *World->GetName(), *ScreenshotName);
|
|
|
|
FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
|
|
if ( LatentActionManager.FindExistingAction<FTakeScreenshotAfterTimeLatentAction>(LatentInfo.CallbackTarget, LatentInfo.UUID) == nullptr )
|
|
{
|
|
LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, new FTakeScreenshotAfterTimeLatentAction(LatentInfo, ScreenshotName, Notes, Options));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UAutomationBlueprintFunctionLibrary::TakeAutomationScreenshotOfUI_Immediate(UObject* WorldContextObject, const FString& ScreenShotName, const FAutomationScreenshotOptions& Options)
|
|
{
|
|
UAutomationBlueprintFunctionLibrary::FinishLoadingBeforeScreenshot();
|
|
|
|
if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
|
|
{
|
|
if (UGameViewportClient* GameViewport = WorldContextObject->GetWorld()->GetGameViewport())
|
|
{
|
|
TSharedPtr<SViewport> Viewport = GameViewport->GetGameViewportWidget();
|
|
if (Viewport.IsValid())
|
|
{
|
|
TArray<FColor> OutColorData;
|
|
FIntVector OutSize;
|
|
if (FSlateApplication::Get().TakeScreenshot(Viewport.ToSharedRef(), OutColorData, OutSize))
|
|
{
|
|
#if WITH_AUTOMATION_TESTS
|
|
// For UI, we only care about what the final image looks like. So don't compare alpha channel.
|
|
// In editor, scene is rendered into a PF_B8G8R8A8 RT and then copied over to the R10B10G10A2 swapchain back buffer and
|
|
// this copy ignores alpha. In game, however, scene is directly rendered into the back buffer and the alpha values are
|
|
// already meaningless at that stage.
|
|
for (int32 Idx = 0; Idx < OutColorData.Num(); ++Idx)
|
|
{
|
|
OutColorData[Idx].A = 0xff;
|
|
}
|
|
|
|
// The screenshot taker deletes itself later.
|
|
FAutomationScreenshotTaker* TempObject = new FAutomationScreenshotTaker(World, ScreenShotName, TEXT(""), Options);
|
|
|
|
FAutomationScreenshotData Data = BuildScreenshotData(World->GetName(), ScreenShotName, OutSize.X, OutSize.Y);
|
|
|
|
// Copy the relevant data into the metadata for the screenshot.
|
|
Data.bHasComparisonRules = true;
|
|
Data.ToleranceRed = Options.ToleranceAmount.Red;
|
|
Data.ToleranceGreen = Options.ToleranceAmount.Green;
|
|
Data.ToleranceBlue = Options.ToleranceAmount.Blue;
|
|
Data.ToleranceAlpha = Options.ToleranceAmount.Alpha;
|
|
Data.ToleranceMinBrightness = Options.ToleranceAmount.MinBrightness;
|
|
Data.ToleranceMaxBrightness = Options.ToleranceAmount.MaxBrightness;
|
|
Data.bIgnoreAntiAliasing = Options.bIgnoreAntiAliasing;
|
|
Data.bIgnoreColors = Options.bIgnoreColors;
|
|
Data.MaximumLocalError = Options.MaximumLocalError;
|
|
Data.MaximumGlobalError = Options.MaximumGlobalError;
|
|
|
|
if (UGameViewportClient* GameViewportClient = AutomationCommon::GetAnyGameViewportClient())
|
|
{
|
|
GameViewportClient->OnScreenshotCaptured().Broadcast(OutSize.X, OutSize.Y, OutColorData);
|
|
}
|
|
#endif
|
|
|
|
return true; //-V773
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::TakeAutomationScreenshotOfUI(UObject* WorldContextObject, FLatentActionInfo LatentInfo, const FString& Name, const FAutomationScreenshotOptions& Options)
|
|
{
|
|
if (TakeAutomationScreenshotOfUI_Immediate(WorldContextObject, Name, Options))
|
|
{
|
|
FLatentActionManager& LatentActionManager = WorldContextObject->GetWorld()->GetLatentActionManager();
|
|
if ( LatentActionManager.FindExistingAction<FTakeScreenshotAfterTimeLatentAction>(LatentInfo.CallbackTarget, LatentInfo.UUID) == nullptr )
|
|
{
|
|
LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, new FWaitForScreenshotComparisonLatentAction(LatentInfo));
|
|
}
|
|
}
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::EnableStatGroup(UObject* WorldContextObject, FName GroupName)
|
|
{
|
|
#if STATS
|
|
if (FGameThreadStatsData* StatsData = FLatestGameThreadStatsData::Get().Latest)
|
|
{
|
|
const FString GroupNameString = FString(TEXT("STATGROUP_")) + GroupName.ToString();
|
|
const FName GroupNameFull = FName(*GroupNameString, EFindName::FNAME_Find);
|
|
if(StatsData->GroupNames.Contains(GroupNameFull))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (APlayerController* TargetPC = UGameplayStatics::GetPlayerController(WorldContextObject, 0))
|
|
{
|
|
TargetPC->ConsoleCommand( FString(TEXT("stat ")) + GroupName.ToString() + FString(TEXT(" -nodisplay")), /*bWriteToLog=*/false);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::DisableStatGroup(UObject* WorldContextObject, FName GroupName)
|
|
{
|
|
#if STATS
|
|
if (FGameThreadStatsData* StatsData = FLatestGameThreadStatsData::Get().Latest)
|
|
{
|
|
const FString GroupNameString = FString(TEXT("STATGROUP_")) + GroupName.ToString();
|
|
const FName GroupNameFull = FName(*GroupNameString, EFindName::FNAME_Find);
|
|
|
|
if (!StatsData->GroupNames.Contains(GroupNameFull))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (APlayerController* TargetPC = UGameplayStatics::GetPlayerController(WorldContextObject, 0))
|
|
{
|
|
TargetPC->ConsoleCommand(FString(TEXT("stat ")) + GroupName.ToString() + FString(TEXT(" -nodisplay")), /*bWriteToLog=*/false);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if STATS
|
|
template <EComplexStatField::Type ValueType, bool bCallCount = false>
|
|
float HelperGetStat(FName StatName)
|
|
{
|
|
if (FGameThreadStatsData* StatsData = FLatestGameThreadStatsData::Get().Latest)
|
|
{
|
|
if (const FComplexStatMessage* StatMessage = StatsData->GetStatData(StatName))
|
|
{
|
|
if(bCallCount)
|
|
{
|
|
return (float)StatMessage->GetValue_CallCount(ValueType);
|
|
}
|
|
else
|
|
{
|
|
return (float)FPlatformTime::ToMilliseconds64(StatMessage->GetValue_Duration(ValueType));
|
|
}
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
FText WarningOut = FText::Format(LOCTEXT("StatNotFound", "Could not find stat data for {0}, did you call ToggleStatGroup with enough time to capture data?"), FText::FromName(StatName));
|
|
FMessageLog("PIE").Warning(WarningOut);
|
|
UE_LOG(AutomationFunctionLibrary, Warning, TEXT("%s"), *WarningOut.ToString());
|
|
#endif
|
|
|
|
return 0.f;
|
|
}
|
|
#endif
|
|
|
|
float UAutomationBlueprintFunctionLibrary::GetStatIncAverage(FName StatName)
|
|
{
|
|
#if STATS
|
|
return HelperGetStat<EComplexStatField::IncAve>(StatName);
|
|
#else
|
|
return 0.0f;
|
|
#endif
|
|
}
|
|
|
|
float UAutomationBlueprintFunctionLibrary::GetStatIncMax(FName StatName)
|
|
{
|
|
#if STATS
|
|
return HelperGetStat<EComplexStatField::IncMax>(StatName);
|
|
#else
|
|
return 0.0f;
|
|
#endif
|
|
}
|
|
|
|
float UAutomationBlueprintFunctionLibrary::GetStatExcAverage(FName StatName)
|
|
{
|
|
#if STATS
|
|
return HelperGetStat<EComplexStatField::ExcAve>(StatName);
|
|
#else
|
|
return 0.0f;
|
|
#endif
|
|
}
|
|
|
|
float UAutomationBlueprintFunctionLibrary::GetStatExcMax(FName StatName)
|
|
{
|
|
#if STATS
|
|
return HelperGetStat<EComplexStatField::ExcMax>(StatName);
|
|
#else
|
|
return 0.0f;
|
|
#endif
|
|
}
|
|
|
|
float UAutomationBlueprintFunctionLibrary::GetStatCallCount(FName StatName)
|
|
{
|
|
#if STATS
|
|
return HelperGetStat<EComplexStatField::IncAve, /*bCallCount=*/true>(StatName);
|
|
#else
|
|
return 0.0f;
|
|
#endif
|
|
}
|
|
|
|
bool UAutomationBlueprintFunctionLibrary::AreAutomatedTestsRunning()
|
|
{
|
|
return GIsAutomationTesting;
|
|
}
|
|
|
|
class FWaitForLoadingToFinish : public FPendingLatentAction
|
|
{
|
|
public:
|
|
FWaitForLoadingToFinish(const FLatentActionInfo& LatentInfo, UObject* InWorldContextObject, const FAutomationWaitForLoadingOptions& InOptions)
|
|
: ExecutionFunction(LatentInfo.ExecutionFunction)
|
|
, OutputLink(LatentInfo.Linkage)
|
|
, CallbackTarget(LatentInfo.CallbackTarget)
|
|
, WorldPtr(InWorldContextObject ? InWorldContextObject->GetWorld() : nullptr)
|
|
, Options(InOptions)
|
|
{
|
|
UAutomationBlueprintFunctionLibrary::FinishLoadingBeforeScreenshot();
|
|
|
|
WaitingFrames = 0;
|
|
LastLoadTime = FPlatformTime::Seconds();
|
|
|
|
if (Options.WaitForReplicationToSettle)
|
|
{
|
|
if (UWorld* MyWorld = GetWorld())
|
|
{
|
|
FOnActorSpawned::FDelegate ActorSpawnedDelegate = FOnActorSpawned::FDelegate::CreateRaw(this, &FWaitForLoadingToFinish::OnActorSpawned);
|
|
ActorSpawnedDelegateHandle = MyWorld->AddOnActorSpawnedHandler(ActorSpawnedDelegate);
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual ~FWaitForLoadingToFinish()
|
|
{
|
|
if (UWorld* MyWorld = GetWorld())
|
|
{
|
|
MyWorld->RemoveOnActorSpawnedHandler(ActorSpawnedDelegateHandle);
|
|
}
|
|
}
|
|
|
|
UWorld* GetWorld()
|
|
{
|
|
if (UWorld* World = WorldPtr.Get())
|
|
{
|
|
return World;
|
|
}
|
|
else
|
|
{
|
|
if (UGameEngine* GameEngine = Cast<UGameEngine>(GEngine))
|
|
{
|
|
return GameEngine->GetGameWorld();
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void OnActorSpawned(AActor* SpawnedActor)
|
|
{
|
|
if (SpawnedActor->GetLocalRole() != ROLE_Authority)
|
|
{
|
|
bActorReplicationDetected = true;
|
|
}
|
|
}
|
|
|
|
bool AnyLevelStreaming()
|
|
{
|
|
// Make sure we finish all level streaming
|
|
if (UWorld* World = GetWorld())
|
|
{
|
|
for (ULevelStreaming* LevelStreaming : World->GetStreamingLevels())
|
|
{
|
|
// See whether there's a level with a pending request.
|
|
if (LevelStreaming)
|
|
{
|
|
if (LevelStreaming->HasLoadRequestPending())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
virtual void UpdateOperation(FLatentResponse& Response) override
|
|
{
|
|
bool bResetWaiting = false;
|
|
|
|
if (IsAsyncLoading())
|
|
{
|
|
bResetWaiting = true;
|
|
}
|
|
else if (AnyLevelStreaming())
|
|
{
|
|
bResetWaiting = true;
|
|
}
|
|
else if (bActorReplicationDetected)
|
|
{
|
|
bResetWaiting = true;
|
|
bActorReplicationDetected = false;
|
|
}
|
|
|
|
if (bResetWaiting)
|
|
{
|
|
WaitingFrames = 0;
|
|
LastLoadTime = FPlatformTime::Seconds();
|
|
}
|
|
else
|
|
{
|
|
WaitingFrames++;
|
|
}
|
|
|
|
const double WaitingTime = (FPlatformTime::Seconds() - LastLoadTime);
|
|
|
|
// Needs to have been both 60 frames, and at least 5 seconds from the last load event.
|
|
if (WaitingFrames > 60 && WaitingTime > 5)
|
|
{
|
|
Response.FinishAndTriggerIf(true, ExecutionFunction, OutputLink, CallbackTarget);
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
// Returns a human readable description of the latent operation's current state
|
|
virtual FString GetDescription() const override
|
|
{
|
|
return TEXT("Waiting For Loading");
|
|
}
|
|
#endif
|
|
|
|
private:
|
|
FName ExecutionFunction;
|
|
int32 OutputLink;
|
|
FWeakObjectPtr CallbackTarget;
|
|
|
|
TWeakObjectPtr<UWorld> WorldPtr;
|
|
|
|
FDelegateHandle ActorSpawnedDelegateHandle;
|
|
|
|
FAutomationWaitForLoadingOptions Options;
|
|
|
|
int32 WaitingFrames = 0;
|
|
double LastLoadTime = 0;
|
|
|
|
bool bActorReplicationDetected = false;
|
|
};
|
|
|
|
|
|
void UAutomationBlueprintFunctionLibrary::AutomationWaitForLoading(UObject* WorldContextObject, FLatentActionInfo LatentInfo, FAutomationWaitForLoadingOptions Options)
|
|
{
|
|
if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
|
|
{
|
|
FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
|
|
if (LatentActionManager.FindExistingAction<FWaitForLoadingToFinish>(LatentInfo.CallbackTarget, LatentInfo.UUID) == nullptr)
|
|
{
|
|
LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, new FWaitForLoadingToFinish(LatentInfo, WorldContextObject, Options));
|
|
}
|
|
}
|
|
}
|
|
|
|
UAutomationEditorTask* UAutomationBlueprintFunctionLibrary::TakeHighResScreenshot(int32 ResX, int32 ResY, FString Filename, ACameraActor* Camera, bool bMaskEnabled, bool bCaptureHDR, EComparisonTolerance ComparisonTolerance, FString ComparisonNotes, float Delay, bool bForceGameView)
|
|
{
|
|
UAutomationEditorTask* Task = NewObject<UAutomationEditorTask>();
|
|
FGCObjectScopeGuard TaskGuard(Task);
|
|
|
|
#if WITH_EDITOR
|
|
if (FModuleManager::Get().IsModuleLoaded("LevelEditor"))
|
|
{
|
|
if (uint32(ResX) <= GetMax2DTextureDimension() && uint32(ResY) <= GetMax2DTextureDimension())
|
|
{
|
|
FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor");
|
|
SLevelViewport* LevelViewport = LevelEditor.GetFirstActiveLevelViewport().Get();
|
|
bool bNeedGameViewToggle = bForceGameView && !LevelViewport->IsInGameView();
|
|
if (bNeedGameViewToggle)
|
|
{
|
|
LevelViewport->ToggleGameView();
|
|
}
|
|
|
|
// Move Viewport to Camera
|
|
bool bNeedCameraChange = Camera != nullptr;
|
|
if (bNeedCameraChange)
|
|
{
|
|
FLevelEditorViewportClient& LevelViewportClient = LevelViewport->GetLevelViewportClient();
|
|
// We set the actor lock (pilot mode) and force the viewport to match the camera now.
|
|
// We unset the actor lock later when the screenshot is done. See FScreenshotTakenState.SetDone().
|
|
LevelViewportClient.SetActorLock(Camera);
|
|
LevelViewportClient.UpdateViewForLockedActor();
|
|
LevelViewportClient.bDisableInput = true;
|
|
LevelViewportClient.bEnableFading = false;
|
|
}
|
|
|
|
FinishLoadingBeforeScreenshot();
|
|
|
|
Task->BindTask(MakeUnique<FScreenshotTakenState>(bNeedGameViewToggle, bNeedCameraChange));
|
|
|
|
// Delay taking the screenshot by a few frames
|
|
FTSTicker::GetCoreTicker().AddTicker(TEXT("ScreenshotDelay"), Delay, [LevelViewport, ComparisonTolerance, ComparisonNotes, Filename, ResX, ResY, bMaskEnabled, bCaptureHDR](float) {
|
|
FHighResScreenshotConfig& HighResScreenshotConfig = GetHighResScreenshotConfig();
|
|
HighResScreenshotConfig.SetResolution(ResX, ResY);
|
|
HighResScreenshotConfig.SetFilename(Filename);
|
|
HighResScreenshotConfig.SetMaskEnabled(bMaskEnabled);
|
|
HighResScreenshotConfig.SetHDRCapture(bCaptureHDR);
|
|
|
|
LevelViewport->GetActiveViewport()->TakeHighResScreenShot();
|
|
#if WITH_AUTOMATION_TESTS
|
|
if (GIsAutomationTesting)
|
|
{
|
|
if (FAutomationTestBase* CurrentTest = FAutomationTestFramework::Get().GetCurrentTest())
|
|
{
|
|
FString Context = CurrentTest->GetTestContext();
|
|
if (Context.IsEmpty()) { Context = CurrentTest->GetTestName(); }
|
|
FAutomationScreenshotOptions ComparisonOptions = FAutomationScreenshotOptions(ComparisonTolerance);
|
|
FAutomationHighResScreenshotGrabber* TempObject = new FAutomationHighResScreenshotGrabber(Context, Filename, ComparisonNotes, ComparisonOptions);
|
|
} //-V773
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
);
|
|
|
|
return Task;
|
|
}
|
|
|
|
UE_LOG(AutomationFunctionLibrary, Error, TEXT("Screenshot size exceeds the maximum allowed texture size (%d x %d)"), GetMax2DTextureDimension(), GetMax2DTextureDimension());
|
|
}
|
|
#endif
|
|
return Task;
|
|
}
|
|
|
|
bool UAutomationBlueprintFunctionLibrary::CompareImageAgainstReference(FString InImagePath, FString ComparisonName, EComparisonTolerance InTolerance, FString InNotes, UObject* WorldContextObject)
|
|
{
|
|
#if WITH_AUTOMATION_TESTS
|
|
if (GIsAutomationTesting)
|
|
{
|
|
const FString ImageExtension = FPaths::GetExtension(InImagePath);
|
|
const EImageFormat ImageFormat = ImageWrapperHelper::GetImageFormat(ImageExtension);
|
|
|
|
IImageWrapperModule& ImageWrapperModule = FModuleManager::GetModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
|
|
TSharedPtr<IImageWrapper> ImageReader = ImageWrapperModule.CreateImageWrapper(ImageFormat);
|
|
if (!ImageReader.IsValid())
|
|
{
|
|
UE_LOG(AutomationFunctionLibrary, Error, TEXT("Unable to locate image processor for {0} file format"), *ImageExtension);
|
|
return false;
|
|
}
|
|
|
|
TArray64<uint8> ImageData;
|
|
const bool OpenSuccess = FFileHelper::LoadFileToArray(ImageData, *InImagePath);
|
|
|
|
if (!OpenSuccess)
|
|
{
|
|
UE_LOG(AutomationFunctionLibrary, Error, TEXT("Unable to read image {0}"), *InImagePath);
|
|
return false;
|
|
}
|
|
|
|
if (!ImageReader->SetCompressed(ImageData.GetData(), ImageData.Num()))
|
|
{
|
|
UE_LOG(AutomationFunctionLibrary, Error, TEXT("Unable to parse image {0}"), *InImagePath);
|
|
return false;
|
|
}
|
|
|
|
if (ImageReader->GetBitDepth() != 8)
|
|
{
|
|
UE_LOG(AutomationFunctionLibrary, Error, TEXT("Automation can only compare 8bit depth channel. {0} has {1}bit per channel."), *InImagePath, *FString::FromInt(ImageReader->GetBitDepth()));
|
|
return false;
|
|
}
|
|
|
|
const int32 Width = ImageReader->GetWidth();
|
|
const int32 Height = ImageReader->GetHeight();
|
|
TArray<FColor> ImageDataDecompressed;
|
|
ImageDataDecompressed.SetNum(Width * Height);
|
|
|
|
if (!ImageReader->GetRaw(ERGBFormat::BGRA, 8, TArrayView64<uint8>((uint8*)ImageDataDecompressed.GetData(), ImageDataDecompressed.Num() * 4)))
|
|
{
|
|
UE_LOG(AutomationFunctionLibrary, Error, TEXT("Unable to decompress image {0}"), *InImagePath);
|
|
return false;
|
|
}
|
|
|
|
if (ComparisonName.IsEmpty())
|
|
{
|
|
ComparisonName = FPaths::GetBaseFilename(InImagePath);
|
|
}
|
|
|
|
FString Context = TEXT("");
|
|
if (FFunctionalTestBase::IsFunctionalTestRunning() && WorldContextObject != nullptr)
|
|
{
|
|
// Functional tests have a different rule to name their test, mainly because part of the full test name is a path.
|
|
// So, to keep name short and still comprehensible, we are going to use the map name + the actor label instead.
|
|
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
|
|
Context = World != nullptr ? World->GetName() : TEXT("UnknownMap");
|
|
Context += TEXT(".") + FFunctionalTestBase::GetRunningTestName();
|
|
}
|
|
|
|
RequestImageComparison(ComparisonName, Width, Height, ImageDataDecompressed, (EAutomationComparisonToleranceLevel)InTolerance, Context, InNotes);
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
UE_LOG(AutomationFunctionLibrary, Warning, TEXT("Can compare image only during test automation."));
|
|
return false;
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::AddTestTelemetryData(FString DataPoint, float Measurement, FString Context)
|
|
{
|
|
if (GIsAutomationTesting)
|
|
{
|
|
if (FAutomationTestBase* CurrentTest = FAutomationTestFramework::Get().GetCurrentTest())
|
|
{
|
|
CurrentTest->AddTelemetryData(DataPoint, Measurement, Context);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::SetTestTelemetryStorage(FString StorageName)
|
|
{
|
|
if (GIsAutomationTesting)
|
|
{
|
|
if (FAutomationTestBase* CurrentTest = FAutomationTestFramework::Get().GetCurrentTest())
|
|
{
|
|
CurrentTest->SetTelemetryStorage(StorageName);
|
|
}
|
|
}
|
|
}
|
|
|
|
FAutomationScreenshotOptions UAutomationBlueprintFunctionLibrary::GetDefaultScreenshotOptionsForGameplay(EComparisonTolerance Tolerance, float Delay)
|
|
{
|
|
FAutomationScreenshotOptions Options;
|
|
Options.Delay = Delay;
|
|
Options.Tolerance = Tolerance;
|
|
Options.bDisableNoisyRenderingFeatures = true;
|
|
Options.bIgnoreAntiAliasing = true;
|
|
Options.SetToleranceAmounts(Tolerance);
|
|
|
|
return Options;
|
|
}
|
|
|
|
FAutomationScreenshotOptions UAutomationBlueprintFunctionLibrary::GetDefaultScreenshotOptionsForRendering(EComparisonTolerance Tolerance, float Delay)
|
|
{
|
|
FAutomationScreenshotOptions Options;
|
|
Options.Delay = Delay;
|
|
Options.Tolerance = Tolerance;
|
|
Options.bDisableNoisyRenderingFeatures = true;
|
|
Options.bIgnoreAntiAliasing = true;
|
|
Options.SetToleranceAmounts(Tolerance);
|
|
|
|
return Options;
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::AddExpectedLogError(FString ExpectedPatternString, int32 Occurrences, bool ExactMatch, bool IsRegex)
|
|
{
|
|
if (FAutomationTestBase* CurrentTest = FAutomationTestFramework::Get().GetCurrentTest())
|
|
{
|
|
CurrentTest->AddExpectedError(ExpectedPatternString, ExactMatch? EAutomationExpectedErrorFlags::Exact:EAutomationExpectedErrorFlags::Contains, Occurrences, IsRegex);
|
|
}
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::AddExpectedPlainLogError(FString ExpectedString, int32 Occurrences, bool ExactMatch)
|
|
{
|
|
if (FAutomationTestBase* CurrentTest = FAutomationTestFramework::Get().GetCurrentTest())
|
|
{
|
|
CurrentTest->AddExpectedErrorPlain(ExpectedString, ExactMatch ? EAutomationExpectedErrorFlags::Exact : EAutomationExpectedErrorFlags::Contains, Occurrences);
|
|
}
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::AddExpectedLogMessage(FString ExpectedPatternString, int32 Occurrences, bool ExactMatch, bool IsRegex)
|
|
{
|
|
if (FAutomationTestBase* CurrentTest = FAutomationTestFramework::Get().GetCurrentTest())
|
|
{
|
|
CurrentTest->AddExpectedMessage(ExpectedPatternString, ExactMatch ? EAutomationExpectedErrorFlags::Exact : EAutomationExpectedErrorFlags::Contains, Occurrences, IsRegex);
|
|
}
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::AddExpectedPlainLogMessage(FString ExpectedString, int32 Occurrences, bool ExactMatch)
|
|
{
|
|
if (FAutomationTestBase* CurrentTest = FAutomationTestFramework::Get().GetCurrentTest())
|
|
{
|
|
CurrentTest->AddExpectedMessagePlain(ExpectedString, ExactMatch ? EAutomationExpectedErrorFlags::Exact : EAutomationExpectedErrorFlags::Contains, Occurrences);
|
|
}
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::SetScalabilityQualityLevelRelativeToMax(UObject* WorldContextObject, int32 Value /*= 1*/)
|
|
{
|
|
Scalability::FQualityLevels Quality;
|
|
Quality.SetFromSingleQualityLevelRelativeToMax(Value);
|
|
Scalability::SetQualityLevels(Quality, true);
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::SetScalabilityQualityToEpic(UObject* WorldContextObject)
|
|
{
|
|
Scalability::FQualityLevels Quality;
|
|
Quality.SetFromSingleQualityLevelRelativeToMax(0);
|
|
Scalability::SetQualityLevels(Quality, true);
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::SetScalabilityQualityToLow(UObject* WorldContextObject)
|
|
{
|
|
Scalability::FQualityLevels Quality;
|
|
Quality.SetFromSingleQualityLevel(0);
|
|
Scalability::SetQualityLevels(Quality, true);
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::SetEditorViewportViewMode(EViewModeIndex Index)
|
|
{
|
|
#if WITH_EDITOR
|
|
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor");
|
|
|
|
if (TSharedPtr<ILevelEditor> LevelEditor = LevelEditorModule.GetFirstLevelEditor())
|
|
{
|
|
for (TSharedPtr<SLevelViewport> LevelViewport : LevelEditor->GetViewports())
|
|
{
|
|
if (LevelViewport.IsValid())
|
|
{
|
|
if (TSharedPtr<FEditorViewportClient> Viewport = LevelViewport->GetViewportClient())
|
|
{
|
|
Viewport->SetViewMode(Index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::SetEditorActiveViewportViewMode(EViewModeIndex Index)
|
|
{
|
|
#if WITH_EDITOR
|
|
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor");
|
|
if (SLevelViewport* LevelViewport = LevelEditorModule.GetFirstActiveLevelViewport().Get())
|
|
{
|
|
if (TSharedPtr<FEditorViewportClient> Viewport = LevelViewport->GetViewportClient())
|
|
{
|
|
Viewport->SetViewMode(Index);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
EViewModeIndex UAutomationBlueprintFunctionLibrary::GetEditorActiveViewportViewMode()
|
|
{
|
|
#if WITH_EDITOR
|
|
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor");
|
|
if (SLevelViewport* LevelViewport = LevelEditorModule.GetFirstActiveLevelViewport().Get())
|
|
{
|
|
if (TSharedPtr<FEditorViewportClient> Viewport = LevelViewport->GetViewportClient())
|
|
{
|
|
return Viewport->GetViewMode();
|
|
}
|
|
}
|
|
#endif
|
|
return EViewModeIndex::VMI_Unknown;
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::SetEditorActiveViewportWireframeOpacity(float Opacity)
|
|
{
|
|
#if WITH_EDITOR
|
|
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor");
|
|
if (SLevelViewport* LevelViewport = LevelEditorModule.GetFirstActiveLevelViewport().Get())
|
|
{
|
|
if (TSharedPtr<FEditorViewportClient> Viewport = LevelViewport->GetViewportClient())
|
|
{
|
|
Viewport->WireframeOpacity = Opacity;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
float UAutomationBlueprintFunctionLibrary::GetEditorActiveViewportWireframeOpacity()
|
|
{
|
|
#if WITH_EDITOR
|
|
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor");
|
|
if (SLevelViewport* LevelViewport = LevelEditorModule.GetFirstActiveLevelViewport().Get())
|
|
{
|
|
if (TSharedPtr<FEditorViewportClient> Viewport = LevelViewport->GetViewportClient())
|
|
{
|
|
return Viewport->WireframeOpacity;
|
|
}
|
|
}
|
|
#endif
|
|
return 0.0f;
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::SetEditorViewportVisualizeBuffer( FName BufferName )
|
|
{
|
|
#if WITH_EDITOR
|
|
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor");
|
|
|
|
if (TSharedPtr<ILevelEditor> LevelEditor = LevelEditorModule.GetFirstLevelEditor())
|
|
{
|
|
for (TSharedPtr<SLevelViewport> LevelViewport : LevelEditor->GetViewports())
|
|
{
|
|
if (LevelViewport.IsValid())
|
|
{
|
|
if (TSharedPtr<FEditorViewportClient> Viewport = LevelViewport->GetViewportClient())
|
|
{
|
|
Viewport->ChangeBufferVisualizationMode(BufferName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::AddTestInfo(const FString& InLogItem)
|
|
{
|
|
if (FAutomationTestBase* CurrentTest = FAutomationTestFramework::Get().GetCurrentTest())
|
|
{
|
|
CurrentTest->AddInfo(InLogItem);
|
|
}
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::AddTestWarning(const FString& InLogItem)
|
|
{
|
|
if (FAutomationTestBase* CurrentTest = FAutomationTestFramework::Get().GetCurrentTest())
|
|
{
|
|
CurrentTest->AddWarning(InLogItem);
|
|
}
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::AddTestError(const FString& InLogItem)
|
|
{
|
|
if (FAutomationTestBase* CurrentTest = FAutomationTestFramework::Get().GetCurrentTest())
|
|
{
|
|
CurrentTest->AddError(InLogItem);
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|
|
|