Files
UnrealEngineUWP/Engine/Source/Developer/FunctionalTesting/Private/ScreenshotFunctionalTest.cpp
christopher fiala 6291d6738b Update LightweightStereoTestVariants mode to run ONLY stereo test variants, no baseline, since they've been moved into their own category. Remove support for specifically exempting tests from running stereo variants. Save screenshots even for passed variants for the time being, since it's now possible to have a variant be the only test running.
Now `Automation.EnableStereoTestVariants=1` (off by default) enables ViewRectOffset, and adding `Automation.LightweightStereoTestVariants=1` causes us to ONLY run ViewRectOffset (on by default).

#jira UE-194361
#rb Jerome.Delattre

[CL 30174576 by christopher fiala in ue5-main branch]
2023-12-06 23:16:08 -05:00

219 lines
7.6 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ScreenshotFunctionalTest.h"
#include "Engine/GameViewportClient.h"
#include "AutomationBlueprintFunctionLibrary.h"
#include "Camera/CameraComponent.h"
#include "Camera/PlayerCameraManager.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/Engine.h"
#include "EngineGlobals.h"
#include "Misc/AutomationTest.h"
#include "HighResScreenshot.h"
#include "UnrealClient.h"
#include "Slate/SceneViewport.h"
#include "UObject/AutomationObjectVersion.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "Tests/AutomationCommon.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(ScreenshotFunctionalTest)
AScreenshotFunctionalTest::AScreenshotFunctionalTest( const FObjectInitializer& ObjectInitializer )
: AScreenshotFunctionalTestBase(ObjectInitializer)
, bCameraCutOnScreenshotPrep(true)
, bNeedsVariantRestore(false)
, bShouldDoBaselineTest(false)
, bShouldDoViewRectOffsetVariant(false)
{
}
void AScreenshotFunctionalTest::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
Ar.UsingCustomVersion(FAutomationObjectVersion::GUID);
if (Ar.CustomVer(FAutomationObjectVersion::GUID) < FAutomationObjectVersion::DefaultToScreenshotCameraCutAndFixedTonemapping)
{
bCameraCutOnScreenshotPrep = true;
}
}
void AScreenshotFunctionalTest::PrepareTest()
{
// If variants are enabled and lightweight variants are on, skip the baseline test
bShouldDoViewRectOffsetVariant = FAutomationTestFramework::NeedPerformStereoTestVariants();
bShouldDoBaselineTest = !(bShouldDoViewRectOffsetVariant && FAutomationTestFramework::NeedUseLightweightStereoTestVariants());
// Pre-prep flush to allow rendering to temporary targets and other test resources
UAutomationBlueprintFunctionLibrary::FinishLoadingBeforeScreenshot();
Super::PrepareTest();
// Apply a camera cut if requested
if (bCameraCutOnScreenshotPrep)
{
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (PlayerController && PlayerController->PlayerCameraManager)
{
PlayerController->PlayerCameraManager->SetGameCameraCutThisFrame();
if (ScreenshotCamera)
{
ScreenshotCamera->NotifyCameraCut();
}
}
}
// Post-prep flush deal with any temporary resources allocated during prep before the main test
UAutomationBlueprintFunctionLibrary::FinishLoadingBeforeScreenshot();
}
void AScreenshotFunctionalTest::RequestScreenshot()
{
Super::RequestScreenshot();
if(IsMobilePlatform(GShaderPlatformForFeatureLevel[GMaxRHIFeatureLevel]))
{
// For mobile, use the high res screenshot API to ensure a fixed resolution screenshot is produced.
// This means screenshot comparisons can compare with the output from any device.
FHighResScreenshotConfig& Config = GetHighResScreenshotConfig();
FIntPoint ScreenshotViewportSize = UAutomationBlueprintFunctionLibrary::GetAutomationScreenshotSize(ScreenshotOptions);
if (Config.SetResolution(ScreenshotViewportSize.X, ScreenshotViewportSize.Y, 1.0f))
{
UGameViewportClient* GameViewportClient = AutomationCommon::GetAnyGameViewportClient();
check(GameViewportClient);
GameViewportClient->GetGameViewport()->TakeHighResScreenShot();
}
}
else
{
// Screenshots in Unreal Engine work in this way:
// 1. Call FScreenshotRequest::RequestScreenshot to ask the system to take a screenshot. The screenshot
// will have the same resolution as the current viewport;
// 2. Register a callback to UGameViewportClient::OnScreenshotCaptured() delegate. The call back will be
// called with screenshot pixel data when the shot is taken;
// 3. Wait till the next frame or call FSceneViewport::Invalidate to force a redraw. Screenshot is not
// taken until next draw where UGameViewportClient::ProcessScreenshots or
// FEditorViewportClient::ProcessScreenshots is called to read pixels back from the viewport. It also
// trigger the callback function registered in step 2.
bool bShowUI = false;
FScreenshotRequest::RequestScreenshot(bShowUI);
}
}
void AScreenshotFunctionalTest::OnScreenShotCaptured(int32 InSizeX, int32 InSizeY, const TArray<FColor>& InImageData)
{
UGameViewportClient* GameViewportClient = AutomationCommon::GetAnyGameViewportClient();
check(GameViewportClient);
GameViewportClient->OnScreenshotCaptured().RemoveAll(this);
#if WITH_AUTOMATION_TESTS
const FString Context = AutomationCommon::GetWorldContext(GetWorld());
TArray<uint8> CapturedFrameTrace = AutomationCommon::CaptureFrameTrace(Context, TestLabel);
FAutomationScreenshotData Data = UAutomationBlueprintFunctionLibrary::BuildScreenshotData(Context, TestLabel, InSizeX, InSizeY);
// Copy the relevant data into the metadata for the screenshot.
Data.bHasComparisonRules = true;
Data.ToleranceRed = ScreenshotOptions.ToleranceAmount.Red;
Data.ToleranceGreen = ScreenshotOptions.ToleranceAmount.Green;
Data.ToleranceBlue = ScreenshotOptions.ToleranceAmount.Blue;
Data.ToleranceAlpha = ScreenshotOptions.ToleranceAmount.Alpha;
Data.ToleranceMinBrightness = ScreenshotOptions.ToleranceAmount.MinBrightness;
Data.ToleranceMaxBrightness = ScreenshotOptions.ToleranceAmount.MaxBrightness;
Data.bIgnoreAntiAliasing = ScreenshotOptions.bIgnoreAntiAliasing;
Data.bIgnoreColors = ScreenshotOptions.bIgnoreColors;
Data.MaximumLocalError = ScreenshotOptions.MaximumLocalError;
Data.MaximumGlobalError = ScreenshotOptions.MaximumGlobalError;
// Add the notes
Data.Notes = Notes;
// If variant in use, pass on the name, then restore settings since capture is done
Data.VariantName = CurrentVariantName;
CurrentVariantName = "";
if (bNeedsVariantRestore)
{
GEngine->Exec(nullptr, *VariantRestoreCommand);
bNeedsVariantRestore = false;
}
if (GIsAutomationTesting)
{
FAutomationTestFramework::Get().OnScreenshotCompared.AddUObject(this, &AScreenshotFunctionalTest::OnComparisonComplete);
}
FAutomationTestFramework::Get().OnScreenshotAndTraceCaptured().ExecuteIfBound(InImageData, CapturedFrameTrace, Data);
UE_LOG(LogScreenshotFunctionalTest, Log, TEXT("Screenshot captured as %s"), *Data.ScreenshotName);
#endif
}
void AScreenshotFunctionalTest::StartTest()
{
if (bShouldDoBaselineTest)
{
bShouldDoBaselineTest = false;
}
else if (bShouldDoViewRectOffsetVariant)
{
bShouldDoViewRectOffsetVariant = false;
SetupVariant("ViewRectOffset", "r.Test.ViewRectOffset 5", "r.Test.ViewRectOffset 0");
}
Super::StartTest();
}
void AScreenshotFunctionalTest::OnScreenshotTakenAndCompared()
{
FAutomationTestBase* CurrentTest = FAutomationTestFramework::Get().GetCurrentTest();
bool bSkipDueToError = FAutomationTestFramework::Get().NeedUseLightweightStereoTestVariants() && (!CurrentTest || CurrentTest->HasAnyErrors());
// If we still need to perform any variants, loop back here
if (bShouldDoViewRectOffsetVariant && !bSkipDueToError)
{
// Re-prepare test if necessary
if (bCameraCutOnScreenshotPrep)
{
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (PlayerController && PlayerController->PlayerCameraManager)
{
PlayerController->PlayerCameraManager->SetGameCameraCutThisFrame();
if (ScreenshotCamera)
{
ScreenshotCamera->NotifyCameraCut();
}
}
}
StartTest();
}
else
{
// This ends the test and reports results
Super::OnScreenshotTakenAndCompared();
}
}
void AScreenshotFunctionalTest::SetupVariant(FString VariantName, FString SetupCommand, FString RestoreCommand)
{
// Set up variant
GEngine->Exec(nullptr, *SetupCommand);
// Save variant name and command needed to restore in OnScreenShotCaptured
CurrentVariantName = VariantName;
if (!RestoreCommand.IsEmpty())
{
VariantRestoreCommand = RestoreCommand;
bNeedsVariantRestore = true;
}
}