Giant_Beast_2025/Plugins/MetaXR/Source/MRUtilityKitEditor/Private/MRUtilityKitDistanceMap.spec.cpp

215 lines
7.1 KiB
C++

// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "DistanceMapTestData.h"
#include "Editor.h"
#include "Editor/UnrealEdEngine.h"
#include "Engine/CanvasRenderTarget2D.h"
#include "HAL/PlatformFileManager.h"
#include "Misc/EngineVersionComparison.h"
#include "MRUtilityKitDistanceMapGenerator.h"
#include "MRUtilityKitSubsystem.h"
#include "TestHelper.h"
#include "Tests/AutomationEditorCommon.h"
#include "TextureResource.h"
#include "UnrealEdGlobals.h"
static void WriteTGA(const FString& FilePath, const TArray<FColor>& Pixels, int32 Width, int32 Height)
{
// Create file
IFileHandle* FileHandle = FPlatformFileManager::Get().GetPlatformFile().OpenWrite(*FilePath);
if (FileHandle)
{
// TGA File Header
uint8 TGAHeader[18] = {};
TGAHeader[2] = 2; // Uncompressed Type
TGAHeader[12] = Width & 0xFF;
TGAHeader[13] = (Width >> 8) & 0xFF;
TGAHeader[14] = Height & 0xFF;
TGAHeader[15] = (Height >> 8) & 0xFF;
TGAHeader[16] = 32; // Bits per Pixel
TGAHeader[17] = 0x20; // Top-Down, Non-Interlaced
// Write TGA Header
FileHandle->Write(TGAHeader, sizeof(TGAHeader));
// Write Pixels
for (int32 i = 0; i < Width * Height; i++)
{
const FColor& Pixel = Pixels[i];
uint8 BGRA[4] = { Pixel.B, Pixel.G, Pixel.R, Pixel.A };
FileHandle->Write(BGRA, sizeof(BGRA));
}
// Close the file
delete FileHandle;
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Failed to open file for writing: %s"), *FilePath);
}
}
static TArray<FColor> ReadTGAFromMemory(const uint8* Data, size_t DataSize)
{
TArray<FColor> Pixels;
// Read TGA Header
uint8 TGAHeader[18];
memcpy(TGAHeader, Data, sizeof(TGAHeader));
// Get image dimensions from header
const int32 Width = TGAHeader[12] | (TGAHeader[13] << 8);
const int32 Height = TGAHeader[14] | (TGAHeader[15] << 8);
if (Width * Height > DataSize)
{
return {};
}
// Resize pixel array
Pixels.SetNumUninitialized(Width * Height);
// Read Pixels
uint32 Offset = sizeof(TGAHeader);
for (int32 i = 0; i < Width * Height; i++)
{
uint8 BGRA[4];
memcpy(BGRA, Data + Offset, sizeof(BGRA));
Offset += sizeof(BGRA);
// Convert from BGRA to RGBA
Pixels[i] = FColor(BGRA[2], BGRA[1], BGRA[0], BGRA[3]);
}
return Pixels;
}
static TArray<FColor> ReadTGAFromFile(const FString& FilePath)
{
IFileHandle* FileHandle = FPlatformFileManager::Get().GetPlatformFile().OpenRead(*FilePath);
TArray<uint8_t> Data;
Data.SetNum(FileHandle->Size());
FileHandle->Read(Data.GetData(), Data.Num());
return ReadTGAFromMemory(Data.GetData(), Data.Num());
}
#if UE_VERSION_OLDER_THAN(5, 5, 0)
BEGIN_DEFINE_SPEC(FMRUKDistanceMapSpec, TEXT("MR Utility Kit"), EAutomationTestFlags::ProductFilter | EAutomationTestFlags::ApplicationContextMask)
#else
BEGIN_DEFINE_SPEC(FMRUKDistanceMapSpec, TEXT("MR Utility Kit"), EAutomationTestFlags::ProductFilter | EAutomationTestFlags_ApplicationContextMask)
#endif
END_DEFINE_SPEC(FMRUKDistanceMapSpec)
void FMRUKDistanceMapSpec::Define()
{
Describe(TEXT("Distance map"), [this] {
BeforeEach([this]() {
// Load map
const auto ContentDir = FPaths::ProjectContentDir();
FAutomationEditorCommonUtils::LoadMap(ContentDir + "/Common/Maps/TestLevel.umap");
StartPIE(true);
});
BeforeEach(EAsyncExecution::ThreadPool, []() {
while (!GEditor->IsPlayingSessionInEditor())
{
// Wait until play session starts
FGenericPlatformProcess::Yield();
}
});
It(TEXT("Capture distance map"), [this] {
const auto World = GEditor->GetPIEWorldContext()->World();
const auto GameInstance = World->GetGameInstance();
UMRUKSubsystem* Subsystem = GameInstance->GetSubsystem<UMRUKSubsystem>();
Subsystem->LoadSceneFromJsonString(ExampleRoomJson);
// Create and setup distance map generator
const FActorSpawnParameters Params{};
AMRUKDistanceMapGenerator* DistanceMapGenerator = World->SpawnActor<AMRUKDistanceMapGenerator>(Params);
DistanceMapGenerator->SetActorLocation(FVector(0.0, 0.0, 200.0));
DistanceMapGenerator->SetActorRotation(FRotator::MakeFromEuler(FVector(0.0, -90.0, 0.0)));
UCanvasRenderTarget2D* RenderTarget = Cast<UCanvasRenderTarget2D>(DistanceMapGenerator->CaptureDistanceMap());
FTextureRenderTargetResource* RenderTargetResource = RenderTarget->GameThread_GetRenderTargetResource();
// Create an array to store the pixel data
TArray<FColor> PixelData;
// Read the pixel data from the Render Target
ENQUEUE_RENDER_COMMAND(ReadSurfaceCommand)
(
[RenderTargetResource, &PixelData](FRHICommandListImmediate& RHICmdList) {
const FTextureRHIRef Texture2DRHI = RenderTargetResource->GetRenderTargetTexture();
RHICmdList.ReadSurfaceData(
Texture2DRHI,
FIntRect(0, 0, Texture2DRHI->GetSizeX(), Texture2DRHI->GetSizeY()),
PixelData,
FReadSurfaceDataFlags());
});
// Wait for the rendering thread to finish executing the command
FlushRenderingCommands();
// Compare result
const TArray<FColor> ExpectedPixels = ReadTGAFromMemory(DistanceMapTestData, sizeof(DistanceMapTestData));
bool Success = true;
if (!TestEqual(TEXT("Pixel count matches"), PixelData.Num(), ExpectedPixels.Num()))
{
Success = false;
}
for (int32 I = 0; I < PixelData.Num() && Success; ++I)
{
if (!TestEqual(TEXT("R channel matches"), PixelData[I].R, ExpectedPixels[I].R))
{
Success = false;
break;
}
if (!TestEqual(TEXT("G channel matches"), PixelData[I].G, ExpectedPixels[I].G))
{
Success = false;
break;
}
if (!TestEqual(TEXT("B channel matches"), PixelData[I].B, ExpectedPixels[I].B))
{
Success = false;
break;
}
if (!TestEqual(TEXT("A channel matches"), PixelData[I].A, ExpectedPixels[I].A))
{
Success = false;
break;
}
}
if (!Success)
{
const FString& TestResultsPath = FPaths::Combine(FPaths::ProjectIntermediateDir(), "TestResults");
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
if (!PlatformFile.DirectoryExists(*TestResultsPath))
{
PlatformFile.CreateDirectory(*TestResultsPath);
}
const FString& ActualPixelsFilename = FPaths::CreateTempFilename(*TestResultsPath, TEXT("DistanceMapTest_ActualPixels"), TEXT(".tga"));
const FString& ExpectedPixelsFilename = FPaths::CreateTempFilename(*TestResultsPath, TEXT("DistanceMapTest_ExpectedPixels"), TEXT(".tga"));
// The resulting binary data can be converted to C array with the tool found here https://github.com/AntumDeluge/bin2header
WriteTGA(ActualPixelsFilename, PixelData, RenderTarget->SizeX, RenderTarget->SizeY);
WriteTGA(ExpectedPixelsFilename, PixelData, RenderTarget->SizeX, RenderTarget->SizeY);
UE_LOG(LogTemp, Warning, TEXT("Expected pixels have been saved as tga file in %s"), *ExpectedPixelsFilename);
UE_LOG(LogTemp, Warning, TEXT("Actual pixels have been saved as tga file in %s"), *ActualPixelsFilename);
}
});
// Caution: Order of these statements is important
AfterEach(EAsyncExecution::ThreadPool, []() {
while (GEditor->IsPlayingSessionInEditor())
{
// Wait until play session ends
FGenericPlatformProcess::Yield();
}
});
AfterEach([]() {
// Request end of play session
GUnrealEd->RequestEndPlayMap();
});
});
}