You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
The issue was that the code that captures screenshots on a device and sends them was the editor was setting a 'path' variable that represented what a screenshot *would* be saved at on the local system. (The screenshot was not actually saved, it's returned as raw data in the message). This meant that the editor would try to save for screenshots to a local path that did not exist (e.g. g:\settings\ on xbox) and fail. The fixes here are - 1) Add some warnings to the code that tries to save screenshots if that fails 2) Change the names of the screenshot functions in AutomationCommon to clarify what they should be used for. 3) Change the 'path' variable in the message to screenshot name. 4) Calculate the path to use for screenshots on the editor. #tests ran EngineTest on WIn64, PS4, and XboxOne (which now passes). #jira UE-69399 #rb swarm #ROBOMERGE-SOURCE: CL 11733277 in //UE4/Release-4.25/... via CL 11733309 #ROBOMERGE-BOT: RELEASE (Release-4.25Plus -> Main) (v656-11643781) [CL 11733341 by andrew grant in Main branch]
1160 lines
38 KiB
C++
1160 lines
38 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"
|
|
|
|
#if WITH_EDITOR
|
|
#include "SLevelViewport.h"
|
|
#endif
|
|
|
|
#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_DEV_AUTOMATION_TESTS || WITH_PERF_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 FSceneViewExtensionBase
|
|
{
|
|
public:
|
|
FAutomationViewExtension(const FAutoRegister& AutoRegister, UWorld* InWorld, FAutomationScreenshotOptions& InOptions, float InCurrentTimeToSimulate)
|
|
: FSceneViewExtensionBase(AutoRegister)
|
|
, WorldPtr(InWorld)
|
|
, Options(InOptions)
|
|
, CurrentTime(InCurrentTimeToSimulate)
|
|
{
|
|
}
|
|
|
|
/** 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.CurrentWorldTime = Options.OverrideTimeTo;
|
|
InViewFamily.CurrentRealTime = Options.OverrideTimeTo;
|
|
InViewFamily.DeltaWorldTime = 0;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
virtual void BeginRenderViewFamily(FSceneViewFamily& InViewFamily) {}
|
|
virtual void PreRenderViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& InViewFamily) {}
|
|
virtual void PreRenderView_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneView& InView) {}
|
|
|
|
virtual bool IsActiveThisFrame(class FViewport* InViewport) const
|
|
{
|
|
if (InViewport)
|
|
{
|
|
FViewportClient* Client = InViewport->GetClient();
|
|
if (Client)
|
|
{
|
|
return WorldPtr->GetWorld() == Client->GetWorld();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** We always want to go last. */
|
|
virtual int32 GetPriority() const override { return MIN_int32; }
|
|
|
|
private:
|
|
TWeakObjectPtr<UWorld> WorldPtr;
|
|
FAutomationScreenshotOptions Options;
|
|
float CurrentTime;
|
|
};
|
|
|
|
FAutomationTestScreenshotEnvSetup::FAutomationTestScreenshotEnvSetup()
|
|
: DefaultFeature_AntiAliasing(TEXT("r.DefaultFeature.AntiAliasing"))
|
|
, DefaultFeature_AutoExposure(TEXT("r.DefaultFeature.AutoExposure"))
|
|
, DefaultFeature_MotionBlur(TEXT("r.DefaultFeature.MotionBlur"))
|
|
, PostProcessAAQuality(TEXT("r.PostProcessAAQuality"))
|
|
, 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"))
|
|
, 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);
|
|
PostProcessAAQuality.Set(0);
|
|
MotionBlurQuality.Set(0);
|
|
ScreenSpaceReflectionQuality.Set(0);
|
|
ContactShadows.Set(0);
|
|
EyeAdaptationQuality.Set(0);
|
|
TonemapperGamma.Set(2.2f);
|
|
//TonemapperSharpen.Set(0);
|
|
}
|
|
else if (InOutOptions.bDisableTonemapping)
|
|
{
|
|
EyeAdaptationQuality.Set(0);
|
|
TonemapperGamma.Set(2.2f);
|
|
//TonemapperSharpen.Set(0);
|
|
}
|
|
|
|
// 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 = GEngine->GameViewport)
|
|
{
|
|
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();
|
|
PostProcessAAQuality.Restore();
|
|
MotionBlurQuality.Restore();
|
|
ScreenSpaceReflectionQuality.Restore();
|
|
EyeAdaptationQuality.Restore();
|
|
ContactShadows.Restore();
|
|
TonemapperGamma.Restore();
|
|
//TonemapperSharpen.Restore();
|
|
SecondaryScreenPercentage.Restore();
|
|
|
|
AutomationViewExtension.Reset();
|
|
|
|
if (UGameViewportClient* ViewportClient = GEngine->GameViewport)
|
|
{
|
|
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& InName, const FString& InNotes, FAutomationScreenshotOptions InOptions)
|
|
: World(InWorld)
|
|
, Name(InName)
|
|
, Notes(InNotes)
|
|
, Options(InOptions)
|
|
, bNeedsViewportSizeRestore(false)
|
|
{
|
|
EnvSetup.Setup(InWorld, Options);
|
|
|
|
if (!FPlatformProperties::HasFixedResolution())
|
|
{
|
|
FSceneViewport* GameViewport = GEngine->GameViewport ? GEngine->GameViewport->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 = InWorld && EditorEngine && EditorEngine->WorldIsPIEInNewViewport(InWorld);
|
|
|
|
if (!bIsPIEViewport || bIsNewViewport)
|
|
#endif
|
|
{
|
|
ViewportRestoreSize = GameViewport->GetSize();
|
|
FIntPoint ScreenshotViewportSize = UAutomationBlueprintFunctionLibrary::GetAutomationScreenshotSize(InOptions);
|
|
GameViewport->SetViewportSize(ScreenshotViewportSize.X, ScreenshotViewportSize.Y);
|
|
bNeedsViewportSizeRestore = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
FlushRenderingCommands();
|
|
|
|
GEngine->GameViewport->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);
|
|
|
|
if (GEngine->GameViewport)
|
|
{
|
|
// remove before we restore the viewport's size - a resize can trigger a redraw, which would trigger OnScreenshotCaptured() again (endless loop)
|
|
GEngine->GameViewport->OnScreenshotCaptured().RemoveAll(this);
|
|
}
|
|
|
|
FWorldDelegates::LevelRemovedFromWorld.RemoveAll(this);
|
|
|
|
if (!FPlatformProperties::HasFixedResolution() && bNeedsViewportSizeRestore)
|
|
{
|
|
if (GEngine->GameViewport)
|
|
{
|
|
FSceneViewport* GameViewport = GEngine->GameViewport->GetGameViewport();
|
|
GameViewport->SetViewportSize(ViewportRestoreSize.X, ViewportRestoreSize.Y);
|
|
}
|
|
}
|
|
|
|
EnvSetup.Restore();
|
|
|
|
FAutomationTestFramework::Get().NotifyScreenshotTakenAndCompared();
|
|
}
|
|
|
|
void GrabScreenShot(int32 InSizeX, int32 InSizeY, const TArray<FColor>& InImageData)
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
if (World.IsValid())
|
|
{
|
|
FAutomationScreenshotData Data = AutomationCommon::BuildScreenshotData(World->GetName(), Name, 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.ScreenshotName);
|
|
|
|
if ( GIsAutomationTesting )
|
|
{
|
|
FAutomationTestFramework::Get().OnScreenshotCompared.AddRaw(this, &FAutomationScreenshotTaker::OnComparisonComplete);
|
|
FScreenshotRequest::OnScreenshotRequestProcessed().RemoveAll(this);
|
|
return;
|
|
}
|
|
}
|
|
|
|
delete this;
|
|
}
|
|
|
|
void OnScreenshotProcessed()
|
|
{
|
|
UE_LOG(AutomationFunctionLibrary, Log, TEXT("Screenshot processed, but not compared."));
|
|
|
|
// If it's done being processed
|
|
delete this;
|
|
}
|
|
|
|
void OnComparisonComplete(const FAutomationScreenshotCompareResults& CompareResults)
|
|
{
|
|
FAutomationTestFramework::Get().OnScreenshotCompared.RemoveAll(this);
|
|
|
|
if (FAutomationTestBase* CurrentTest = FAutomationTestFramework::Get().GetCurrentTest())
|
|
{
|
|
CurrentTest->AddEvent(CompareResults.ToAutomationEvent(Name));
|
|
}
|
|
|
|
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 && InWorld == World.Get())
|
|
{
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
TWeakObjectPtr<UWorld> World;
|
|
|
|
FString Name;
|
|
FString Notes;
|
|
FAutomationScreenshotOptions Options;
|
|
|
|
FAutomationTestScreenshotEnvSetup EnvSetup;
|
|
FIntPoint ViewportRestoreSize;
|
|
bool bNeedsViewportSizeRestore;
|
|
};
|
|
|
|
class FAutomationHighResScreenshotGrabber
|
|
{
|
|
public:
|
|
FAutomationHighResScreenshotGrabber(const FString& InContext, const FString& InName, const FString& InNotes, FAutomationScreenshotOptions InOptions)
|
|
: Context(InContext)
|
|
, Name(InName)
|
|
, 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 = AutomationCommon::BuildScreenshotData(Context, Name, 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.ScreenshotName);
|
|
|
|
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(Name));
|
|
}
|
|
|
|
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 Name;
|
|
FString Notes;
|
|
FAutomationScreenshotOptions Options;
|
|
};
|
|
|
|
#endif
|
|
|
|
class FScreenshotTakenState : public FAutomationTaskStatusBase
|
|
{
|
|
public:
|
|
FScreenshotTakenState()
|
|
{
|
|
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_DEV_AUTOMATION_TESTS || WITH_PERF_AUTOMATION_TESTS)
|
|
FAutomationTestFramework::Get().OnScreenshotCompared.RemoveAll(this);
|
|
#endif
|
|
FScreenshotRequest::OnScreenshotRequestProcessed().RemoveAll(this);
|
|
};
|
|
|
|
virtual void SetDone() override
|
|
{
|
|
FScreenshotRequest::OnScreenshotRequestProcessed().RemoveAll(this);
|
|
Done = true;
|
|
};
|
|
|
|
void OnComparisonComplete(const FAutomationScreenshotCompareResults& CompareResults)
|
|
{
|
|
FAutomationTestFramework::Get().OnScreenshotCompared.RemoveAll(this);
|
|
SetDone();
|
|
};
|
|
};
|
|
|
|
UAutomationBlueprintFunctionLibrary::UAutomationBlueprintFunctionLibrary(const class FObjectInitializer& Initializer)
|
|
: Super(Initializer)
|
|
{
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::FinishLoadingBeforeScreenshot()
|
|
{
|
|
// Finish compiling the shaders if the platform doesn't require cooked data.
|
|
if (!FPlatformProperties::RequiresCookedData())
|
|
{
|
|
GShaderCompilingManager->FinishAllCompilation();
|
|
FModuleManager::GetModuleChecked<IAutomationControllerModule>("AutomationController").GetAutomationController()->ResetAutomationTestTimeout(TEXT("shader compilation"));
|
|
}
|
|
|
|
FlushAsyncLoading();
|
|
|
|
// Make sure we finish all level streaming
|
|
if (UGameEngine* GameEngine = Cast<UGameEngine>(GEngine))
|
|
{
|
|
if (UWorld* GameWorld = GameEngine->GetGameWorld())
|
|
{
|
|
GameWorld->FlushLevelStreaming(EFlushLevelStreamingType::Full);
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
bool UAutomationBlueprintFunctionLibrary::TakeAutomationScreenshotInternal(UObject* WorldContextObject, const FString& Name, const FString& Notes, FAutomationScreenshotOptions Options)
|
|
{
|
|
UAutomationBlueprintFunctionLibrary::FinishLoadingBeforeScreenshot();
|
|
|
|
#if (WITH_DEV_AUTOMATION_TESTS || WITH_PERF_AUTOMATION_TESTS)
|
|
FAutomationScreenshotTaker* TempObject = new FAutomationScreenshotTaker(WorldContextObject ? WorldContextObject->GetWorld() : nullptr, Name, Notes, Options);
|
|
#endif
|
|
|
|
FScreenshotRequest::RequestScreenshot(false);
|
|
return true; //-V773
|
|
}
|
|
|
|
void UAutomationBlueprintFunctionLibrary::TakeAutomationScreenshot(UObject* WorldContextObject, FLatentActionInfo LatentInfo, const FString& Name, const FString& Notes, const FAutomationScreenshotOptions& Options)
|
|
{
|
|
if ( GIsAutomationTesting )
|
|
{
|
|
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, Name, 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;
|
|
}
|
|
|
|
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(WorldContextObject, 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->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& Name, 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_DEV_AUTOMATION_TESTS || WITH_PERF_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, Name, TEXT(""), Options);
|
|
|
|
FAutomationScreenshotData Data = AutomationCommon::BuildScreenshotData(World->GetName(), Name, 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;
|
|
|
|
GEngine->GameViewport->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 StatMessage->GetValue_CallCount(ValueType);
|
|
}
|
|
else
|
|
{
|
|
return FPlatformTime::ToMilliseconds(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)
|
|
: ExecutionFunction(LatentInfo.ExecutionFunction)
|
|
, OutputLink(LatentInfo.Linkage)
|
|
, CallbackTarget(LatentInfo.CallbackTarget)
|
|
{
|
|
WaitingFrames = 0;
|
|
UAutomationBlueprintFunctionLibrary::FinishLoadingBeforeScreenshot();
|
|
}
|
|
|
|
virtual ~FWaitForLoadingToFinish()
|
|
{
|
|
}
|
|
|
|
bool AnyLevelStreaming()
|
|
{
|
|
// Make sure we finish all level streaming
|
|
if (UGameEngine* GameEngine = Cast<UGameEngine>(GEngine))
|
|
{
|
|
if (UWorld* GameWorld = GameEngine->GetGameWorld())
|
|
{
|
|
for (ULevelStreaming* LevelStreaming : GameWorld->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;
|
|
}
|
|
|
|
if (bResetWaiting)
|
|
{
|
|
WaitingFrames = 0;
|
|
}
|
|
else
|
|
{
|
|
WaitingFrames++;
|
|
}
|
|
|
|
if (WaitingFrames > 60)
|
|
{
|
|
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;
|
|
|
|
int32 WaitingFrames;
|
|
};
|
|
|
|
|
|
void UAutomationBlueprintFunctionLibrary::AutomationWaitForLoading(UObject* WorldContextObject, FLatentActionInfo LatentInfo)
|
|
{
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
|
|
UAutomationEditorTask* UAutomationBlueprintFunctionLibrary::TakeHighResScreenshot(int32 ResX, int32 ResY, FString Filename, ACameraActor* Camera, bool bMaskEnabled, bool bCaptureHDR, EComparisonTolerance ComparisonTolerance, FString ComparisonNotes)
|
|
{
|
|
UAutomationEditorTask* Task = NewObject<UAutomationEditorTask>();
|
|
FGCObjectScopeGuard TaskGuard(Task);
|
|
|
|
#if WITH_EDITOR
|
|
if (FModuleManager::Get().IsModuleLoaded("LevelEditor"))
|
|
{
|
|
FHighResScreenshotConfig& HighResScreenshotConfig = GetHighResScreenshotConfig();
|
|
if (HighResScreenshotConfig.SetResolution(ResX, ResY))
|
|
{
|
|
HighResScreenshotConfig.SetFilename(Filename);
|
|
HighResScreenshotConfig.SetMaskEnabled(bMaskEnabled);
|
|
HighResScreenshotConfig.SetHDRCapture(bCaptureHDR);
|
|
|
|
FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor");
|
|
SLevelViewport* LevelViewport = LevelEditor.GetFirstActiveLevelViewport().Get();
|
|
|
|
// Move Viewport to Camera
|
|
if (Camera)
|
|
{
|
|
FLevelEditorViewportClient& LevelViewportClient = LevelViewport->GetLevelViewportClient();
|
|
// We set the actor lock (pilot mode) and force the viewport to match the camera now.
|
|
// Then we unset the actor lock to avoid users to move their asset through the viewport unwantedly.
|
|
LevelViewportClient.SetActorLock(Camera);
|
|
LevelViewportClient.MoveCameraToLockedActor();
|
|
LevelViewportClient.SetActorLock(nullptr);
|
|
}
|
|
|
|
FinishLoadingBeforeScreenshot();
|
|
|
|
#if (WITH_DEV_AUTOMATION_TESTS || WITH_PERF_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
|
|
Task->BindTask(MakeUnique<FScreenshotTakenState>());
|
|
LevelViewport->GetActiveViewport()->TakeHighResScreenShot();
|
|
|
|
return Task;
|
|
}
|
|
|
|
UE_LOG(AutomationFunctionLibrary, Error, TEXT("Screenshot size exceeds the maximum allowed texture size (%d x %d)"), GetMax2DTextureDimension(), GetMax2DTextureDimension());
|
|
}
|
|
#endif
|
|
return Task;
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (FAutomationTestBase* CurrentTest = FAutomationTestFramework::Get().GetCurrentTest())
|
|
{
|
|
CurrentTest->AddExpectedError(ExpectedPatternString, 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);
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|