Files
UnrealEngine/Engine/Source/Runtime/Renderer/Private/SceneCulling/HierarchicalSpatialHashGrid.h
2025-05-18 13:04:45 +08:00

480 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "EngineDefines.h"
#include "Experimental/Containers/RobinHoodHashTable.h"
#include "SceneCullingDefinitions.h"
#include "Rendering/RenderingSpatialHash.h"
#include "SpanAllocator.h"
#include "Tasks/Task.h"
class FScenePreUpdateChangeSet;
class FScenePostUpdateChangeSet;
// Test implemented for perf comparison, may not scale exactly as size grows & not fully featured.
#define USE_STATIC_HASH_TABLE 0
#if USE_STATIC_HASH_TABLE
template <typename InKeyType, typename InValueType>
class TSpatialHashMap
{
public:
using KeyType = InKeyType;
using ValueType = InValueType;
struct FElement
{
KeyType Key;
ValueType Value;
};
struct FElementId
{
inline FElementId(int32 InIndex = INDEX_NONE)
: Index(InIndex)
{
}
int32 Index = INDEX_NONE;
int32 GetIndex() const { return Index; }
bool IsValid() const { return Index != INDEX_NONE; }
};
using Hasher = TDefaultMapHashableKeyFuncs<KeyType, ValueType, false>;
void Empty()
{
Elements.Reset();
HashTable.Clear();
}
void Reserve(int32 Num)
{
Elements.Reserve(Num);
}
inline FElementId FindIdByHash(uint16 Hash, const KeyType& Location) const
{
for (uint16 Index = HashTable.First(Hash); HashTable.IsValid(Index); Index = HashTable.Next(Index))
{
if (Hasher::Matches(Elements[Index].Key, Location))
{
return FElementId{ int32(Index) };
}
}
return FElementId{};
}
inline FElementId FindId(const KeyType& Location) const
{
uint16 Hash = uint16(Hasher::GetKeyHash(Location));
return FindIdByHash(Hash, Location);
}
inline FElementId FindOrAddId(const KeyType& Location, const ValueType& Value)
{
uint16 Hash = uint16(Hasher::GetKeyHash(Location));
FElementId Id = FindIdByHash(Hash, Location);
if (!Id.IsValid())
{
int32 NewIndex = Elements.Num();
Elements.Add(FElement{ Location, Value });
HashTable.Add(Hash, NewIndex);
Id = FElementId{ NewIndex };
}
return Id;
}
inline int32 GetMaxIndex() const { return Elements.Num(); }
inline int32 Num() const { return Elements.Num(); }
inline FElement& GetByElementId(FElementId Id) { return Elements[Id.Index]; }
inline const FElement& GetByElementId(FElementId Id) const { return Elements[Id.Index]; }
class FConstIteratorType
{
friend TSpatialHashMap<KeyType, ValueType>;
const TSpatialHashMap<KeyType, ValueType>& TheMap;
int32 Index;
inline FConstIteratorType(const TSpatialHashMap<KeyType, ValueType>& InTheMap, bool bIsStartIterator) : TheMap(InTheMap)
{
if (bIsStartIterator)
{
Index = 0;
}
else
{
Index = TheMap.Elements.Num();
}
}
public:
inline bool operator ==(const FConstIteratorType& Rhs) const
{
return Index == Rhs.Index;
}
inline bool operator !=(const FConstIteratorType& Rhs) const
{
return Index != Rhs.Index;
}
inline const FElement& operator*() const
{
return TheMap.Elements[Index];
}
inline FConstIteratorType& operator++()
{
++Index;
return *this;
}
inline FElementId GetElementId() const
{
return FElementId{ Index };
}
};
inline FConstIteratorType begin() const
{
return FConstIteratorType(*this, true);
}
inline FConstIteratorType end() const
{
return FConstIteratorType(*this, false);
}
TArray<FElement> Elements;
TStaticHashTable<32u * 1024u, 32u * 1024u> HashTable;
};
#endif
inline FInt64Vector FloorToInt64Vector(const FVector& Vector)
{
return FInt64Vector(FMath::FloorToInt(Vector.X), FMath::FloorToInt(Vector.Y), FMath::FloorToInt(Vector.Z));
};
template <typename BlockTraitsType>
class THierarchicalSpatialHashGrid
{
public:
using FBlockLoc = typename BlockTraitsType::FBlockLoc;
static constexpr int32 CellBlockDimLog2 = BlockTraitsType::CellBlockDimLog2;
static constexpr int32 kMaxLevel = 64;
static constexpr int32 NumCellsPerBlockLog2 = CellBlockDimLog2 * 3; // number of bits needed for a local cell ID
static constexpr int32 CellBlockDim = 1 << CellBlockDimLog2;
static constexpr uint32 LocalCellCoordMask = uint32(CellBlockDim) - 1U;
static constexpr int32 CellBlockSize = CellBlockDim * CellBlockDim * CellBlockDim;
using FLocation64 = RenderingSpatialHash::TLocation<int64>;
using FLocation32 = RenderingSpatialHash::TLocation<int32>;
using FLocation8 = RenderingSpatialHash::TLocation<int8>;
using FInt8Vector3 = UE::Math::TIntVector3<int8>;
template <typename ScalarType>
struct TFootprint
{
using FIntVector3 = UE::Math::TIntVector3<ScalarType>;
template <typename LambdaType>
inline void ForEach(LambdaType Lambda)
{
for (ScalarType Z = Min.Z; Z <= Max.Z; ++Z)
{
for (ScalarType Y = Min.Y; Y <= Max.Y; ++Y)
{
for (ScalarType X = Min.X; X <= Max.X; ++X)
{
RenderingSpatialHash::TLocation<ScalarType> L;
L.Coord = typename RenderingSpatialHash::TLocation<ScalarType>::FIntVector3{ X, Y, Z };
L.Level = Level;
Lambda(L);
}
}
}
}
FIntVector3 Min;
FIntVector3 Max;
int32 Level;
};
using FFootprint64 = TFootprint<int64>;
using FFootprint32 = TFootprint<int32>;
using FFootprint8 = TFootprint<int8>;
struct FCellBlock
{
static uint32 constexpr CoarseCellMaskDimLog2 = 2U; // 2^2 == 4 -> 4x4x4 == 64
static uint32 constexpr CoarseCellMaskDim = 1U << CoarseCellMaskDimLog2; // 2^2 == 4 -> 4x4x4 == 64
static uint32 constexpr CoarseCellMaskCoordMask = (1U << CoarseCellMaskDimLog2) - 1u; // 2^2 == 4 -> 4x4x4 == 64
inline static int32 CalcCellOffset(const FInt8Vector3& Loc)
{
return ((int32(Loc.Z) * CellBlockDim) + int32(Loc.Y)) * CellBlockDim + int32(Loc.X);
};
inline int32 GetCellGridOffset(const FInt8Vector3& Loc) const
{
return GridOffset + CalcCellOffset(Loc);
}
inline static uint32 CalcCellMaskOffset(const FInt8Vector3& MaskLoc)
{
return (((uint32(MaskLoc.Z) << CoarseCellMaskDimLog2) + uint32(MaskLoc.Y)) << CoarseCellMaskDimLog2) + uint32(MaskLoc.X);
};
inline static uint64 CalcCellMask(const FInt8Vector3& Loc)
{
FInt8Vector3 MaskLoc = Loc >> (CellBlockDimLog2 - CoarseCellMaskDimLog2);
return 1ULL << CalcCellMaskOffset(MaskLoc);
};
inline static uint64 BuildFootPrintMask(const FFootprint8& FootprintInBlock)
{
FFootprint8 FootprintInCoarse = FootprintInBlock;
FootprintInCoarse.Min = FootprintInCoarse.Min >> (CellBlockDimLog2 - CoarseCellMaskDimLog2);
FootprintInCoarse.Max = FootprintInCoarse.Max >> (CellBlockDimLog2 - CoarseCellMaskDimLog2);
uint64 Result = 0ULL;
// TODO: make this not stupid... figure out bit intervals etc, do incrementally.
FootprintInCoarse.ForEach([&](const FLocation8& Loc)
{
Result |= (1ULL << CalcCellMaskOffset(Loc.Coord));
});
return Result;
}
uint64 CoarseCellMask = 0ULL;
int32 GridOffset = INDEX_NONE;
int32 NumItemChunks = 0;
};
THierarchicalSpatialHashGrid() = default;
THierarchicalSpatialHashGrid(double MinCellSize, double MaxCellSize)
{
// Subtract 1 to make sure that intuitively, when we set a cell size of exact POT e.g., 4096, that is what we get (not the correctly conservative 8192).
FirstLevel = CalcLevel(MinCellSize - 1.0);
LastLevel = CalcLevel(MaxCellSize - 1.0);
check(GetCellSize(FirstLevel) >= MinCellSize && GetCellSize(FirstLevel - 1) < MinCellSize);
check(GetCellSize(LastLevel) >= MaxCellSize && GetCellSize(LastLevel - 1) < MaxCellSize);
LastLevelCellSize = GetCellSize(LastLevel);
// TODO: figure out number of bits in coordinate type minus those in the smallest level,
MaxCullingDistance = WORLD_MAX;
for (int32 Level = 0; Level < kMaxLevel; ++Level)
{
RecCellSizes[Level] = 1.0 / GetCellSize(Level);
}
}
void Empty()
{
HashMap.Empty();
}
inline int32 GetMaxNumBlocks() const { return HashMap.GetMaxIndex(); }
static inline int32 CalcLevel(float Size)
{
return RenderingSpatialHash::CalcLevel(Size);
};
static inline int32 CalcLevelFromRadius(float Radius)
{
return CalcLevel(Radius * 2.0f);
};
static inline double GetCellSize(int32 Level)
{
return RenderingSpatialHash::GetCellSize(Level);
};
inline double GetRecCellSize(int32 Level) const
{
return RecCellSizes[Level];
}
inline FLocation64 ToCellLoc(int32 Level, const FVector& WorldPos) const
{
FLocation64 Result;
double RecLevelCellSize = GetRecCellSize(Level);
Result.Level = Level;
Result.Coord = FloorToInt64Vector(WorldPos * RecLevelCellSize);
return Result;
};
inline FFootprint64 CalcFootprintSphere(int32 Level, const FVector& Origin, double Radius) const
{
FFootprint64 Footprint;
double RecLevelCellSize = GetRecCellSize(Level);
Footprint.Level = Level;
Footprint.Min = FloorToInt64Vector((Origin - FVector(Radius, Radius, Radius)) * RecLevelCellSize);
Footprint.Max = FloorToInt64Vector((Origin + FVector(Radius, Radius, Radius)) * RecLevelCellSize);
return Footprint;
};
static inline FFootprint64 CalcCellBlockFootprint(const FFootprint64& Footprint)
{
FFootprint64 BlockFootprint;
BlockFootprint.Min = Footprint.Min >> CellBlockDimLog2;
BlockFootprint.Max = Footprint.Max >> CellBlockDimLog2;
BlockFootprint.Level = Footprint.Level + CellBlockDimLog2;
return BlockFootprint;
};
inline FFootprint64 CalcLevelAndFootprint(const FBoxSphereBounds& BoxSphereBounds) const
{
// Can't be lower than this, or the footprint might be larger than 2x2x2, globally the same, can pre-calc.
int32 Level = CalcLevelFromRadius(BoxSphereBounds.SphereRadius);
// Clamp to lowest level represented in the grid
Level = FMath::Max(FirstLevel, Level);
double RecLevelCellSize = GetRecCellSize(Level);
FFootprint64 Footprint;
Footprint.Level = Level;
Footprint.Min = FloorToInt64Vector((BoxSphereBounds.Origin - BoxSphereBounds.BoxExtent) * RecLevelCellSize);
Footprint.Max = FloorToInt64Vector((BoxSphereBounds.Origin + BoxSphereBounds.BoxExtent) * RecLevelCellSize);
return Footprint;
};
inline FLocation64 CalcLevelAndLocation(const FBoxSphereBounds& BoxSphereBounds) const
{
int32 Level = CalcLevelFromRadius(BoxSphereBounds.SphereRadius);
// Clamp to lowest level represented in the grid
Level = FMath::Max(FirstLevel, Level);
return ToCellLoc(Level, BoxSphereBounds.Origin);
};
inline FLocation64 CalcLevelAndLocation(const FVector4d& Sphere) const
{
int32 Level = CalcLevelFromRadius(Sphere.W);
// Clamp to lowest level represented in the grid
Level = FMath::Max(FirstLevel, Level);
return ToCellLoc(Level, FVector(Sphere));
};
inline bool IsValidLevel(int32 Level) const
{
return Level >= FirstLevel && Level <= LastLevel;
}
inline double GetLastLevelCellSize() const
{
return LastLevelCellSize;
}
inline double GetMaxCullingDistance() const
{
return MaxCullingDistance;
}
inline int32 NumLevels() const { return LastLevel - FirstLevel + 1; }
// Padded by half a cell size because of loose bounds
inline FBox CalcCellBounds(const FLocation64& CellLoc) const
{
FBox Box;
double LevelCellSize = GetCellSize(CellLoc.Level);
Box.Min = FVector(CellLoc.Coord) * LevelCellSize;
Box.Max = Box.Min + LevelCellSize;
// Extend extent by half a cell size in all directions
Box.Min -= FVector(LevelCellSize * 0.5);
Box.Max += FVector(LevelCellSize * 0.5);
return Box;
}
// Padded by half a cell size since loose (NOT 1/2 block size!)
inline FBox CalcBlockBounds(const FLocation64& BlockLoc) const
{
FBox Box;
double BlockLevelSize = GetCellSize(BlockLoc.Level);
Box.Min = FVector(BlockLoc.Coord) * BlockLevelSize;
Box.Max = Box.Min + BlockLevelSize;
// Extend extent by half a cell size in all directions
double LevelCellSize = GetCellSize(BlockLoc.Level - CellBlockDimLog2);
Box.Min -= FVector(LevelCellSize * 0.5);
Box.Max += FVector(LevelCellSize * 0.5);
return Box;
}
/**
* The reference location is always the minimum corner, unpadded by loose bounds expansion.
*/
inline FVector3d CalcBlockWorldPosition(const FLocation64& BlockLoc) const
{
return RenderingSpatialHash::CalcWorldPosition(BlockLoc);
}
/**
* The reference location is always the minimum corner, unpadded by loose bounds expansion.
*/
inline FVector3d CalcBlockWorldPosition(const FBlockLoc& BlockLoc) const
{
return BlockLoc.GetWorldPosition();
}
int32 GetFirstLevel() const { return FirstLevel; }
#if USE_STATIC_HASH_TABLE
using FSpatialHashMap = ::TSpatialHashMap<FBlockLoc, FCellBlock>;
using FHashElementId = typename FSpatialHashMap::FElementId;
using FBlockId = typename FSpatialHashMap::FElementId;
#else
struct FHasher : public TDefaultMapKeyFuncs<FBlockLoc, FCellBlock, false>
{
static FORCEINLINE uint32 GetKeyHash(const FBlockLoc& Key)
{
return Key.GetHash();
}
};
using FSpatialHashMap = Experimental::TRobinHoodHashMap<FBlockLoc, FCellBlock, FHasher>;
using FBlockId = Experimental::FHashElementId;
using FHashElementId = Experimental::FHashElementId;
#endif
FCellBlock &GetBlockById(const FBlockId &BlockId) { return HashMap.GetByElementId(BlockId).Value; }
const FCellBlock &GetBlockById(const FBlockId &BlockId) const { return HashMap.GetByElementId(BlockId).Value; }
FBlockLoc GetBlockLocById(const FBlockId &BlockId) const { return HashMap.GetByElementId(BlockId).Key; }
const FSpatialHashMap &GetHashMap() const { return HashMap; };
FSpatialHashMap &GetHashMap() { return HashMap; };
private:
int32 FirstLevel;
int32 LastLevel;
double RecCellSizes[kMaxLevel];
double LastLevelCellSize;
double MaxCullingDistance;
FSpatialHashMap HashMap;
};