Files
UnrealEngineUWP/Engine/Source/Runtime/Renderer/Private/DynamicBVH.cpp
Brian Karis dba24ab6be FDynamicBVH now supports a forest with a spatial hash at the highest level to support LWC without the need for doubles for bounding boxes.
Switched FDynamicBVH to use greedy insertion because branch and bound perf significantly degrades with heavily overlapped leaves.

Added closest point queries using branch and bound.

FBounds is now templated as TBounds.

#rb graham.wihlidal
#robomerge FNNC
#preflight 6216dbf9104496cff8abfc36

[CL 19107993 by Brian Karis in ue5-main branch]
2022-02-23 21:17:53 -05:00

356 lines
8.7 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;
}
Sort( &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;
BVH.Update( BVH.GetBounds( Index ), 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;
BVH.Update( BVH.GetBounds( Index ), 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;
BVH.Update( BVH.GetBounds( Index ), 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;
BVH.Update( BVH.GetBounds( Index ), 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;
}