Files
UnrealEngine/Engine/Source/Developer/FunctionalTesting/Private/ScreenshotFunctionalTest.cpp
2025-05-18 13:04:45 +08:00

298 lines
9.3 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)
, RequestedVariants(false, EVariantType::Num)
, bVariantQueued(false)
{
}
void AScreenshotFunctionalTest::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
Ar.UsingCustomVersion(FAutomationObjectVersion::GUID);
if (Ar.CustomVer(FAutomationObjectVersion::GUID) < FAutomationObjectVersion::DefaultToScreenshotCameraCutAndFixedTonemapping)
{
bCameraCutOnScreenshotPrep = true;
}
}
bool AScreenshotFunctionalTest::RunTest(const TArray<FString>& Params)
{
// Set up which variants are enabled
check(!RequestedVariants.Contains(true));
if (FAutomationTestFramework::NeedPerformStereoTestVariants())
{
RequestedVariants[EVariantType::Baseline] = !FAutomationTestFramework::NeedUseLightweightStereoTestVariants(); // Skip baseline if lightweight variants are active
RequestedVariants[EVariantType::ViewRectOffset] = true;
}
else
{
RequestedVariants[EVariantType::Baseline] = true;
RequestedVariants[EVariantType::ViewRectOffset] = false;
}
// Set up first variant
SetupNextVariant();
// This will call PrepareTest()
return Super::RunTest();
}
void AScreenshotFunctionalTest::PrepareTest()
{
// 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]) && !GEngine->IsStereoscopic3D())
{
// 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
if (!IsRunning())
{
// Don't send the data if the test is no longer running.
return;
}
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;
if (VariantRestoreCommand)
{
GEngine->Exec(nullptr, VariantRestoreCommand);
VariantRestoreCommand = nullptr;
}
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.ScreenshotPath);
#endif
}
void AScreenshotFunctionalTest::OnScreenshotTakenAndCompared()
{
FAutomationTestBase* CurrentTest = FAutomationTestFramework::Get().GetCurrentTest();
bool bSkipRemainingVariants = FAutomationTestFramework::Get().NeedUseLightweightStereoTestVariants() && (!CurrentTest || CurrentTest->HasAnyErrors());
// If we aren't skipping further variants due to a failure, and we still have some remaining, queue the next
if (!bSkipRemainingVariants && SetupNextVariant())
{
ReprepareTest();
}
// Otherwise finish
else
{
Super::OnScreenshotTakenAndCompared();
}
}
bool AScreenshotFunctionalTest::SetupNextVariant()
{
// Find first remaining requested variant
int32 Index = RequestedVariants.Find(true);
if (Index == INDEX_NONE)
{
return false;
}
else
{
// Remove variant from requested variants and set it up
RequestedVariants[Index] = false;
SetupVariant(static_cast<EVariantType>(Index));
return true;
}
}
void AScreenshotFunctionalTest::SetupVariant(EVariantType VariantType)
{
// Get info for requested variant
static const TArray<FVariantInfo> VariantInfoArray =
{
FVariantInfo{ TEXT(""), nullptr, nullptr }, // Baseline
FVariantInfo{ TEXT("ViewRectOffset"), TEXT("r.Test.ViewRectOffset 5"), TEXT("r.Test.ViewRectOffset 0") } // ViewRectOffset
};
const FVariantInfo* VariantInfo = &VariantInfoArray[VariantType];
// Set up variant
if (VariantInfo->SetupCommand)
{
GEngine->Exec(nullptr, VariantInfo->SetupCommand);
}
// Save variant name and command needed to restore in OnScreenShotCaptured
CurrentVariantName = VariantInfo->Name;
if (VariantInfo->RestoreCommand)
{
VariantRestoreCommand = VariantInfo->RestoreCommand;
}
}
void AScreenshotFunctionalTest::ReprepareTest()
{
// Required to reset TSR sequences
OnTestFinished.Broadcast();
RestoreViewSettings();
// Rather than re-use RunFrame/RunTime, use a seprarate set for subsequent variants
// A screenshot will be requested once the given delay has elapsed
VariantFrame = GFrameNumber;
VariantTime = (float)GetWorld()->GetTimeSeconds();
bVariantQueued = true;
PrepareTest();
}
// TSR sequences override this to rely on an internal blueprint instead, but it will still use our Tick() implementation
// Because we call OnTestFinished.Broadcast() in ReprepareTest(), the TSR BP frame counter is reset properly
bool AScreenshotFunctionalTest::IsReady_Implementation()
{
if (bVariantQueued)
{
if ((GetWorld()->GetTimeSeconds() - VariantTime) > ScreenshotOptions.Delay)
{
return int32(GFrameNumber - VariantFrame) > ScreenshotOptions.FrameDelay;
}
return false;
}
else
{
return Super::IsReady_Implementation();
}
}
void AScreenshotFunctionalTest::Tick(float DeltaSeconds)
{
// This section takes over from the main loop to kick off subsequent variants
if (bVariantQueued)
{
if (IsReady())
{
bVariantQueued = false;
StartTest();
}
}
else
{
if (PreparationTimeLimit > 0.f && TotalTime > PreparationTimeLimit)
{
OnTimeout();
}
}
Super::Tick(DeltaSeconds);
}