361 lines
8.8 KiB
C++
361 lines
8.8 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "DynamicBVH.h"
|
|
#include "Misc/AutomationTest.h"
|
|
#include "Misc/OutputDeviceRedirector.h"
|
|
|
|
FMortonArray::FMortonArray( const TArray< FBounds3f >& InBounds )
|
|
: Bounds( InBounds )
|
|
{
|
|
TArray< FSortPair > Unsorted;
|
|
Unsorted.AddUninitialized( Bounds.Num() );
|
|
Sorted.AddUninitialized( Bounds.Num() );
|
|
|
|
FBounds3f TotalBounds;
|
|
for( int i = 0; i < Bounds.Num(); i++ )
|
|
{
|
|
TotalBounds += Bounds[i].Min + Bounds[i].Max;
|
|
}
|
|
|
|
FVector3f Scale = FVector3f( 1.0f ) / ( TotalBounds.Max - TotalBounds.Min );
|
|
FVector3f Bias = -TotalBounds.Min / ( TotalBounds.Max - TotalBounds.Min );
|
|
|
|
for( int i = 0; i < Bounds.Num(); i++ )
|
|
{
|
|
FVector3f CenterLocal = ( Bounds[i].Min + Bounds[i].Max ) * Scale + Bias;
|
|
|
|
uint32 Morton;
|
|
Morton = FMath::MortonCode3( CenterLocal.X * 1023 );
|
|
Morton |= FMath::MortonCode3( CenterLocal.Y * 1023 ) << 1;
|
|
Morton |= FMath::MortonCode3( CenterLocal.Z * 1023 ) << 2;
|
|
|
|
Unsorted[i].Code = Morton;
|
|
Unsorted[i].Index = i;
|
|
}
|
|
|
|
RadixSort32( Sorted.GetData(), Unsorted.GetData(), Unsorted.Num(),
|
|
[&]( FSortPair Pair )
|
|
{
|
|
return Pair.Code;
|
|
} );
|
|
}
|
|
|
|
void FMortonArray::RegenerateCodes( const FRange& Range )
|
|
{
|
|
FBounds3f TotalBounds;
|
|
for( int32 i = Range.Begin; i < Range.End; i++ )
|
|
{
|
|
uint32 Index = Sorted[i].Index;
|
|
TotalBounds += Bounds[ Index ].Min + Bounds[ Index ].Max;
|
|
}
|
|
|
|
FVector3f Scale = FVector3f( 1.0f ) / ( TotalBounds.Max - TotalBounds.Min );
|
|
FVector3f Bias = -TotalBounds.Min / ( TotalBounds.Max - TotalBounds.Min );
|
|
|
|
for( int32 i = Range.Begin; i < Range.End; i++ )
|
|
{
|
|
uint32 Index = Sorted[i].Index;
|
|
|
|
FVector3f CenterLocal = ( Bounds[ Index ].Min + Bounds[ Index ].Max ) * Scale + Bias;
|
|
|
|
uint32 Morton;
|
|
Morton = FMath::MortonCode3( CenterLocal.X * 1023 );
|
|
Morton |= FMath::MortonCode3( CenterLocal.Y * 1023 ) << 1;
|
|
Morton |= FMath::MortonCode3( CenterLocal.Z * 1023 ) << 2;
|
|
|
|
Sorted[i].Code = Morton;
|
|
}
|
|
|
|
Algo::Sort(MakeArrayView(&Sorted[Range.Begin], Range.End - Range.Begin));
|
|
}
|
|
|
|
|
|
inline FVector3f RandomVector( float Min, float Max )
|
|
{
|
|
FVector3f V;
|
|
V.X = FMath::RandRange( Min, Max );
|
|
V.Y = FMath::RandRange( Min, Max );
|
|
V.Z = FMath::RandRange( Min, Max );
|
|
return V;
|
|
}
|
|
|
|
static FBounds3f RandomBounds( float CenterMin, float CenterMax, float ExtentMin, float ExtentMax )
|
|
{
|
|
FVector3f Center = RandomVector( CenterMin, CenterMax );
|
|
FVector3f Extent = RandomVector( ExtentMin, ExtentMax );
|
|
return FBounds3f( { Center - Extent, Center + Extent } );
|
|
}
|
|
|
|
template< uint32 MaxChildren >
|
|
static void RandomBVH( float CenterMin, float CenterMax, float ExtentMin, float ExtentMax, uint32 Num, FDynamicBVH< MaxChildren >& BVH )
|
|
{
|
|
for( uint32 i = 0; i < Num; i++ )
|
|
{
|
|
BVH.Add( RandomBounds( CenterMin, CenterMax, ExtentMin, ExtentMax ), i );
|
|
}
|
|
}
|
|
|
|
void TestBVH_ZeroToZero()
|
|
{
|
|
FMath::RandInit( 17 );
|
|
|
|
FDynamicBVH<4> BVH;
|
|
|
|
uint32 Time0 = FPlatformTime::Cycles();
|
|
|
|
RandomBVH( -64.0f, 64.0f, 1.0f, 4.0f, 65536, BVH );
|
|
|
|
uint32 Num = BVH.GetNumLeaves();
|
|
uint32 Time1 = FPlatformTime::Cycles();
|
|
|
|
float Cost = BVH.GetTotalCost();
|
|
|
|
for( uint32 i = 0; i < Num; i++ )
|
|
{
|
|
BVH.Remove(i);
|
|
}
|
|
|
|
uint32 Time2 = FPlatformTime::Cycles();
|
|
GLog->Logf( TEXT("TestBVH_ZeroToZero add [%.2fms]"), FPlatformTime::ToMilliseconds( Time1 - Time0 ) );
|
|
GLog->Logf( TEXT("TestBVH_ZeroToZero cost %.2f"), Cost );
|
|
GLog->Logf( TEXT("TestBVH_ZeroToZero remove [%.2fms]"), FPlatformTime::ToMilliseconds( Time2 - Time1 ) );
|
|
}
|
|
|
|
void TestBVH_AddRemove()
|
|
{
|
|
FMath::RandInit( 17 );
|
|
|
|
FDynamicBVH<4> BVH;
|
|
RandomBVH( -64.0f, 64.0f, 1.0f, 4.0f, 8192, BVH );
|
|
|
|
uint32 Num = BVH.GetNumLeaves();
|
|
uint32 Time0 = FPlatformTime::Cycles();
|
|
|
|
BVH.NumTested = 0;
|
|
|
|
for( int i = 0; i < 65536; i++ )
|
|
{
|
|
BVH.Add( RandomBounds( -64.0f, 64.0f, 1.0f, 4.0f ), Num );
|
|
BVH.Remove( Num );
|
|
}
|
|
|
|
uint32 Time1 = FPlatformTime::Cycles();
|
|
GLog->Logf( TEXT("TestBVH_AddRemove [%.2fms]"), FPlatformTime::ToMilliseconds( Time1 - Time0 ) );
|
|
GLog->Logf( TEXT("TestBVH_AddRemove tested %.2f"), (float)BVH.NumTested / 65536.0f );
|
|
|
|
BVH.Check();
|
|
}
|
|
|
|
void TestBVH_AddRemoveOverlapped()
|
|
{
|
|
FMath::RandInit( 17 );
|
|
|
|
FDynamicBVH<4> BVH;
|
|
RandomBVH( -64.0f, 64.0f, 16.0f, 64.0f, 8192, BVH );
|
|
|
|
uint32 Num = BVH.GetNumLeaves();
|
|
uint32 Time0 = FPlatformTime::Cycles();
|
|
|
|
BVH.NumTested = 0;
|
|
|
|
for( int i = 0; i < 65536; i++ )
|
|
{
|
|
BVH.Add( RandomBounds( -64.0f, 64.0f, 16.0f, 64.0f ), Num );
|
|
BVH.Remove( Num );
|
|
}
|
|
|
|
uint32 Time1 = FPlatformTime::Cycles();
|
|
GLog->Logf( TEXT("TestBVH_AddRemoveOverlapped [%.2fms]"), FPlatformTime::ToMilliseconds( Time1 - Time0 ) );
|
|
GLog->Logf( TEXT("TestBVH_AddRemoveOverlapped tested %.2f"), (float)BVH.NumTested / 65536.0f );
|
|
|
|
BVH.Check();
|
|
}
|
|
|
|
void TestBVH_BatchAddRemove()
|
|
{
|
|
FMath::RandInit( 17 );
|
|
|
|
FDynamicBVH<4> BVH;
|
|
RandomBVH( -64.0f, 64.0f, 1.0f, 4.0f, 8192, BVH );
|
|
|
|
uint32 Num = BVH.GetNumLeaves();
|
|
uint32 Time0 = FPlatformTime::Cycles();
|
|
|
|
for( int i = 0; i < 256; i++ )
|
|
{
|
|
for( int j = 0; j < 256; j++ )
|
|
BVH.Add( RandomBounds( -64.0f, 64.0f, 1.0f, 4.0f ), Num + j );
|
|
|
|
for( int j = 0; j < 256; j++ )
|
|
BVH.Remove( Num + j );
|
|
}
|
|
|
|
uint32 Time1 = FPlatformTime::Cycles();
|
|
GLog->Logf( TEXT("TestBVH_BatchAddRemove [%.2fms]"), FPlatformTime::ToMilliseconds( Time1 - Time0 ) );
|
|
|
|
BVH.Check();
|
|
}
|
|
|
|
void TestBVH_UpdateTeleport()
|
|
{
|
|
FMath::RandInit( 17 );
|
|
|
|
FDynamicBVH<4> BVH;
|
|
RandomBVH( -64.0f, 64.0f, 1.0f, 4.0f, 8192, BVH );
|
|
|
|
uint32 Num = BVH.GetNumLeaves();
|
|
uint32 Time0 = FPlatformTime::Cycles();
|
|
|
|
for( int i = 0; i < 65536; i++ )
|
|
{
|
|
uint32 Index = FMath::Rand() % Num;
|
|
BVH.Update( RandomBounds( -64.0f, 64.0f, 1.0f, 4.0f ), Index );
|
|
}
|
|
|
|
uint32 Time1 = FPlatformTime::Cycles();
|
|
GLog->Logf( TEXT("TestBVH_UpdateTeleport [%.2fms]"), FPlatformTime::ToMilliseconds( Time1 - Time0 ) );
|
|
|
|
BVH.Check();
|
|
}
|
|
|
|
void TestBVH_UpdateJitter()
|
|
{
|
|
FMath::RandInit( 17 );
|
|
|
|
FDynamicBVH<4> BVH;
|
|
RandomBVH( -64.0f, 64.0f, 1.0f, 4.0f, 8192, BVH );
|
|
|
|
uint32 Num = BVH.GetNumLeaves();
|
|
uint32 Time0 = FPlatformTime::Cycles();
|
|
|
|
for( int i = 0; i < 65536; i++ )
|
|
{
|
|
uint32 Index = FMath::Rand() % Num;
|
|
|
|
FBounds3f Bounds = BVH.GetBounds( Index );
|
|
FVector3f Jitter = RandomVector( -0.01f, 0.01f );
|
|
Bounds.Min += Jitter;
|
|
Bounds.Max += Jitter;
|
|
|
|
BVH.Update( Bounds, Index );
|
|
}
|
|
|
|
uint32 Time1 = FPlatformTime::Cycles();
|
|
GLog->Logf( TEXT("TestBVH_UpdateJitter [%.2fms]"), FPlatformTime::ToMilliseconds( Time1 - Time0 ) );
|
|
|
|
BVH.Check();
|
|
}
|
|
|
|
void TestBVH_Ordered()
|
|
{
|
|
FBounds3f UnitBounds;
|
|
UnitBounds.Min = FVector3f::ZeroVector;
|
|
UnitBounds.Max = FVector3f::OneVector;
|
|
|
|
FDynamicBVH<4> BVH;
|
|
|
|
for( uint32 i = 0; i < 8192; i++ )
|
|
{
|
|
FBounds3f Bounds = UnitBounds;
|
|
Bounds.Min.X += (float)i;
|
|
Bounds.Max.X += (float)i;
|
|
BVH.Add( Bounds, i );
|
|
}
|
|
|
|
GLog->Logf( TEXT("TestBVH_Ordered cost %.2f"), BVH.GetTotalCost() );
|
|
|
|
uint32 Num = BVH.GetNumLeaves();
|
|
|
|
for( int i = 0; i < 1024; i++ )
|
|
{
|
|
uint32 Index = FMath::Rand() % Num;
|
|
FBounds3f Bounds = BVH.GetBounds( Index );
|
|
BVH.Update( Bounds, Index );
|
|
//BVH.Optimize(1);
|
|
}
|
|
|
|
GLog->Logf( TEXT("TestBVH_Ordered cost after %.2f"), BVH.GetTotalCost() );
|
|
|
|
for( int i = 0; i < 65536; i++ )
|
|
{
|
|
uint32 Index = FMath::Rand() % Num;
|
|
FBounds3f Bounds = BVH.GetBounds( Index );
|
|
BVH.Update( Bounds, Index );
|
|
//BVH.Optimize(1);
|
|
}
|
|
|
|
GLog->Logf( TEXT("TestBVH_Ordered cost after %.2f"), BVH.GetTotalCost() );
|
|
}
|
|
|
|
void TestBVH_Optimize()
|
|
{
|
|
FMath::RandInit( 17 );
|
|
|
|
FDynamicBVH<4> BVH;
|
|
RandomBVH( -64.0f, 64.0f, 1.0f, 4.0f, 8192, BVH );
|
|
|
|
GLog->Logf( TEXT("TestBVH_Optimize cost before %.2f"), BVH.GetTotalCost() );
|
|
|
|
uint32 Num = BVH.GetNumLeaves();
|
|
|
|
for( int i = 0; i < 1024; i++ )
|
|
{
|
|
uint32 Index = FMath::Rand() % Num;
|
|
FBounds3f Bounds = BVH.GetBounds( Index );
|
|
BVH.Update( Bounds, Index );
|
|
//BVH.Optimize(1);
|
|
}
|
|
|
|
GLog->Logf( TEXT("TestBVH_Optimize cost after %.2f"), BVH.GetTotalCost() );
|
|
|
|
for( int i = 0; i < 65536; i++ )
|
|
{
|
|
uint32 Index = FMath::Rand() % Num;
|
|
FBounds3f Bounds = BVH.GetBounds( Index );
|
|
BVH.Update( Bounds, Index );
|
|
//BVH.Optimize(1);
|
|
}
|
|
|
|
GLog->Logf( TEXT("TestBVH_Optimize cost after %.2f"), BVH.GetTotalCost() );
|
|
}
|
|
|
|
void TestBVH_Build()
|
|
{
|
|
FMath::RandInit( 17 );
|
|
|
|
FDynamicBVH<4> BVH;
|
|
|
|
uint32 Num = 65536;
|
|
|
|
TArray< FBounds3f > BoundsArray;
|
|
BoundsArray.AddUninitialized( Num );
|
|
|
|
for( uint32 i = 0; i < Num; i++ )
|
|
BoundsArray[i] = RandomBounds( -64.0f, 64.0f, 1.0f, 4.0f );
|
|
|
|
uint32 Time0 = FPlatformTime::Cycles();
|
|
|
|
BVH.Build( BoundsArray, 0 );
|
|
|
|
uint32 Time1 = FPlatformTime::Cycles();
|
|
|
|
GLog->Logf( TEXT("TestBVH_Build [%.2fms]"), FPlatformTime::ToMilliseconds( Time1 - Time0 ) );
|
|
GLog->Logf( TEXT("TestBVH_Build cost %.2f"), BVH.GetTotalCost() );
|
|
}
|
|
|
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST( FTestBVH, "System.Renderer.DynamicBVH", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter );
|
|
bool FTestBVH::RunTest( const FString& Parameters )
|
|
{
|
|
TestBVH_ZeroToZero();
|
|
TestBVH_AddRemove();
|
|
TestBVH_AddRemoveOverlapped();
|
|
TestBVH_BatchAddRemove();
|
|
TestBVH_UpdateTeleport();
|
|
TestBVH_UpdateJitter();
|
|
TestBVH_Ordered();
|
|
TestBVH_Optimize();
|
|
TestBVH_Build();
|
|
|
|
return true;
|
|
}
|