Files
UnrealEngine/Engine/Source/Runtime/Renderer/Private/Lumen/LumenSparseSpanArray.h
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

519 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
LumenSparseSpanArray.h:
=============================================================================*/
#pragma once
#include "CoreMinimal.h"
#include "Engine/EngineTypes.h"
#include "SpanAllocator.h"
// Sparse array with stable indices and contiguous span allocation
template <typename ElementType>
class TSparseSpanArray
{
public:
int32 Num() const
{
return SpanAllocator.GetMaxSize();
}
void Reserve(int32 NumElements)
{
Elements.Reserve(NumElements);
}
int32 AddSpan(int32 NumElements)
{
check(NumElements > 0);
const int32 InsertIndex = SpanAllocator.Allocate(NumElements);
// Resize element array
if (SpanAllocator.GetMaxSize() > Elements.Num())
{
const int32 NumElementsToAdd = SpanAllocator.GetMaxSize() - Elements.Num();
Elements.AddDefaulted(NumElementsToAdd);
AllocatedElementsBitArray.Add(false, NumElementsToAdd);
}
// Reuse existing elements
for (int32 ElementIndex = InsertIndex; ElementIndex < InsertIndex + NumElements; ++ElementIndex)
{
checkSlow(!IsAllocated(ElementIndex));
Elements[ElementIndex] = ElementType();
}
AllocatedElementsBitArray.SetRange(InsertIndex, NumElements, true);
return InsertIndex;
}
void RemoveSpan(int32 FirstElementIndex, int32 NumElements)
{
check(NumElements > 0);
for (int32 ElementIndex = FirstElementIndex; ElementIndex < FirstElementIndex + NumElements; ++ElementIndex)
{
checkSlow(IsAllocated(ElementIndex));
Elements[ElementIndex] = ElementType();
}
SpanAllocator.Free(FirstElementIndex, NumElements);
AllocatedElementsBitArray.SetRange(FirstElementIndex, NumElements, false);
}
void Consolidate()
{
SpanAllocator.Consolidate();
if (Elements.Num() > SpanAllocator.GetMaxSize() * ShrinkThreshold)
{
Elements.SetNum(SpanAllocator.GetMaxSize());
AllocatedElementsBitArray.SetNumUninitialized(SpanAllocator.GetMaxSize());
}
}
void Reset()
{
Elements.Reset();
SpanAllocator.Reset();
AllocatedElementsBitArray.SetNumUninitialized(0);
}
ElementType& operator[](int32 Index)
{
checkSlow(IsAllocated(Index));
return Elements[Index];
}
const ElementType& operator[](int32 Index) const
{
checkSlow(IsAllocated(Index));
return Elements[Index];
}
bool IsAllocated(int32 ElementIndex) const
{
if (ElementIndex < Num())
{
return AllocatedElementsBitArray[ElementIndex];
}
return false;
}
SIZE_T GetAllocatedSize() const
{
return Elements.GetAllocatedSize() + AllocatedElementsBitArray.GetAllocatedSize() + SpanAllocator.GetAllocatedSize();
}
class TRangedForIterator
{
public:
TRangedForIterator(TSparseSpanArray<ElementType>& InArray, int32 InElementIndex)
: Array(InArray)
, ElementIndex(InElementIndex)
{
// Scan for the first valid element.
while (ElementIndex < Array.Num() && !Array.AllocatedElementsBitArray[ElementIndex])
{
++ElementIndex;
}
}
TRangedForIterator operator++()
{
// Scan for the next first valid element.
do
{
++ElementIndex;
} while (ElementIndex < Array.Num() && !Array.AllocatedElementsBitArray[ElementIndex]);
return *this;
}
bool operator!=(const TRangedForIterator& Other) const
{
return ElementIndex != Other.ElementIndex;
}
ElementType& operator*()
{
return Array.Elements[ElementIndex];
}
private:
TSparseSpanArray<ElementType>& Array;
int32 ElementIndex;
};
class TRangedForConstIterator
{
public:
TRangedForConstIterator(const TSparseSpanArray<ElementType>& InArray, int32 InElementIndex)
: Array(InArray)
, ElementIndex(InElementIndex)
{
// Scan for the first valid element.
while (ElementIndex < Array.Num() && !Array.AllocatedElementsBitArray[ElementIndex])
{
++ElementIndex;
}
}
TRangedForConstIterator operator++()
{
// Scan for the next first valid element.
do
{
++ElementIndex;
} while (ElementIndex < Array.Num() && !Array.AllocatedElementsBitArray[ElementIndex]);
return *this;
}
bool operator!=(const TRangedForConstIterator& Other) const
{
return ElementIndex != Other.ElementIndex;
}
const ElementType& operator*() const
{
return Array.Elements[ElementIndex];
}
private:
const TSparseSpanArray<ElementType>& Array;
int32 ElementIndex;
};
// Iterate over all allocated elements (skip free ones)
TRangedForIterator begin() { return TRangedForIterator(*this, 0); }
TRangedForIterator end() { return TRangedForIterator(*this, Num()); }
TRangedForConstIterator begin() const { return TRangedForConstIterator(*this, 0); }
TRangedForConstIterator end() const { return TRangedForConstIterator(*this, Num()); }
private:
// Allocated size needs to be this much bigger than used size before we shrink this array
static constexpr int32 ShrinkThreshold = 2;
TArray<ElementType> Elements;
TBitArray<> AllocatedElementsBitArray;
FSpanAllocator SpanAllocator;
};
template <typename ElementType, uint32 BytesPerChunk = 2 * 1024 * 1024>
class TChunkedSparseArray
{
public:
TChunkedSparseArray() = default;
TChunkedSparseArray(const TChunkedSparseArray& Other)
{
*this = Other;
}
TChunkedSparseArray(TChunkedSparseArray&& Other)
{
*this = MoveTemp(Other);
}
TChunkedSparseArray& operator=(const TChunkedSparseArray& Other)
{
if (&Other != this)
{
Empty();
FreeElementIndexHint = Other.FreeElementIndexHint;
NumAllocatedElements = Other.NumAllocatedElements;
MaxAllocatedElementIndexPlusOne = Other.MaxAllocatedElementIndexPlusOne;
AllocatedElementsBitArray = Other.AllocatedElementsBitArray;
const int32 NumElementChunks = Other.ElementChunks.Num();
ElementChunks.Reserve(NumElementChunks);
for (int32 ChunkIndex = 0; ChunkIndex < NumElementChunks; ++ChunkIndex)
{
ElementChunks.Add((ElementType*)FMemory::Malloc(BytesPerChunk));
}
for (TConstSetBitIterator It(AllocatedElementsBitArray); It && It.GetIndex() < GetMaxSize(); ++It)
{
const int32 ElementIndex = It.GetIndex();
ElementType& Element = (*this)[ElementIndex];
new (&Element) ElementType(Other[ElementIndex]);
}
}
return *this;
}
TChunkedSparseArray& operator=(TChunkedSparseArray&& Other)
{
if (&Other != this)
{
Empty();
FreeElementIndexHint = Other.FreeElementIndexHint;
NumAllocatedElements = Other.NumAllocatedElements;
MaxAllocatedElementIndexPlusOne = Other.MaxAllocatedElementIndexPlusOne;
Other.FreeElementIndexHint = 0;
Other.NumAllocatedElements = 0;
Other.MaxAllocatedElementIndexPlusOne = 0;
AllocatedElementsBitArray = MoveTemp(Other.AllocatedElementsBitArray);
ElementChunks = MoveTemp(Other.ElementChunks);
}
return *this;
}
~TChunkedSparseArray()
{
Empty();
}
void Empty()
{
for (ElementType& Element : *this)
{
Element.~ElementType();
}
for (ElementType* Chunk : ElementChunks)
{
FMemory::Free(Chunk);
}
ElementChunks.Empty();
AllocatedElementsBitArray.Empty();
FreeElementIndexHint = 0;
NumAllocatedElements = 0;
MaxAllocatedElementIndexPlusOne = 0;
}
int32 GetMaxSize() const
{
return MaxAllocatedElementIndexPlusOne;
}
void Reserve(int32 NumElements)
{
const int32 NewNumChunks = FMath::DivideAndRoundUp(NumElements, ElementsPerChunk);
AllocatedElementsBitArray.SetNum(FMath::Max(ElementChunks.Num(), NewNumChunks) * ElementsPerChunk, false);
while (ElementChunks.Num() < NewNumChunks)
{
ElementChunks.Add((ElementType*)FMemory::Malloc(BytesPerChunk));
}
}
int32 AddDefaulted()
{
// Find the first unused element slot or add to the end
int32 InsertIndex;
if (NumAllocatedElements == MaxAllocatedElementIndexPlusOne)
{
check(MaxAllocatedElementIndexPlusOne <= AllocatedElementsBitArray.Num());
InsertIndex = MaxAllocatedElementIndexPlusOne;
++MaxAllocatedElementIndexPlusOne;
if (InsertIndex < AllocatedElementsBitArray.Num())
{
AllocatedElementsBitArray[InsertIndex] = true;
}
}
else
{
check(NumAllocatedElements < MaxAllocatedElementIndexPlusOne);
InsertIndex = AllocatedElementsBitArray.FindAndSetFirstZeroBit(FreeElementIndexHint);
check(InsertIndex < MaxAllocatedElementIndexPlusOne);
}
FreeElementIndexHint = InsertIndex + 1;
++NumAllocatedElements;
// Grow by one chunk if we run out of space
if (MaxAllocatedElementIndexPlusOne > ElementChunks.Num() * ElementsPerChunk)
{
check(InsertIndex + 1 == MaxAllocatedElementIndexPlusOne);
check(InsertIndex == ElementChunks.Num() * ElementsPerChunk);
check(AllocatedElementsBitArray.Num() == ElementChunks.Num() * ElementsPerChunk);
ElementChunks.Add((ElementType*)FMemory::Malloc(BytesPerChunk));
AllocatedElementsBitArray.Add(false, ElementsPerChunk);
AllocatedElementsBitArray[InsertIndex] = true;
}
check(IsAllocated(InsertIndex));
const int32 ChunkIndex = InsertIndex / ElementsPerChunk;
const int32 Index = InsertIndex - ChunkIndex * ElementsPerChunk;
// We can construct in-place because the element is either uninitialized or has already been destructed
new (&ElementChunks[ChunkIndex][Index]) ElementType;
return InsertIndex;
}
void RemoveAt(int32 ElementIndex)
{
check(IsAllocated(ElementIndex));
const int32 ChunkIndex = ElementIndex / ElementsPerChunk;
const int32 Index = ElementIndex - ChunkIndex * ElementsPerChunk;
ElementChunks[ChunkIndex][Index].~ElementType();
AllocatedElementsBitArray[ElementIndex] = false;
--NumAllocatedElements;
FreeElementIndexHint = FMath::Min(FreeElementIndexHint, ElementIndex);
if (ElementIndex + 1 == MaxAllocatedElementIndexPlusOne)
{
MaxAllocatedElementIndexPlusOne = AllocatedElementsBitArray.FindLastFrom(true, ElementIndex - 1) + 1;
}
check(FreeElementIndexHint <= MaxAllocatedElementIndexPlusOne);
}
void Shrink()
{
// Try to keep an additional chunk if the last used chunk is more than half full
const int32 ChunksToKeep = FMath::DivideAndRoundUp(MaxAllocatedElementIndexPlusOne + ElementsPerChunk / 2, ElementsPerChunk);
AllocatedElementsBitArray.SetNumUninitialized(FMath::Min(ElementChunks.Num(), ChunksToKeep) * ElementsPerChunk);
while (ElementChunks.Num() > ChunksToKeep)
{
FMemory::Free(ElementChunks.Last());
ElementChunks.Pop(EAllowShrinking::No);
}
}
ElementType& operator[](int32 ElementIndex)
{
check(IsAllocated(ElementIndex));
const int32 ChunkIndex = ElementIndex / ElementsPerChunk;
const int32 Index = ElementIndex - ChunkIndex * ElementsPerChunk;
return ElementChunks[ChunkIndex][Index];
}
const ElementType& operator[](int32 ElementIndex) const
{
check(IsAllocated(ElementIndex));
const int32 ChunkIndex = ElementIndex / ElementsPerChunk;
const int32 Index = ElementIndex - ChunkIndex * ElementsPerChunk;
return ElementChunks[ChunkIndex][Index];
}
bool IsAllocated(int32 ElementIndex) const
{
if (ElementIndex < GetMaxSize())
{
return AllocatedElementsBitArray[ElementIndex];
}
return false;
}
SIZE_T GetAllocatedSize() const
{
return ElementChunks.Num() * BytesPerChunk + ElementChunks.GetAllocatedSize() + AllocatedElementsBitArray.GetAllocatedSize() + sizeof(TChunkedSparseArray);
}
class TRangedForIterator
{
public:
TRangedForIterator(TChunkedSparseArray<ElementType>& InArray, int32 InElementIndex)
: Array(InArray)
, ElementIndex(InElementIndex)
{
// Scan for the first valid element.
while (ElementIndex < Array.GetMaxSize() && !Array.AllocatedElementsBitArray[ElementIndex])
{
++ElementIndex;
}
}
TRangedForIterator operator++()
{
// Scan for the next first valid element.
do
{
++ElementIndex;
} while (ElementIndex < Array.GetMaxSize() && !Array.AllocatedElementsBitArray[ElementIndex]);
return *this;
}
bool operator!=(const TRangedForIterator& Other) const
{
return ElementIndex != Other.ElementIndex;
}
ElementType& operator*()
{
return Array[ElementIndex];
}
private:
TChunkedSparseArray<ElementType>& Array;
int32 ElementIndex;
};
class TRangedForConstIterator
{
public:
TRangedForConstIterator(const TChunkedSparseArray<ElementType>& InArray, int32 InElementIndex)
: Array(InArray)
, ElementIndex(InElementIndex)
{
// Scan for the first valid element.
while (ElementIndex < Array.GetMaxSize() && !Array.AllocatedElementsBitArray[ElementIndex])
{
++ElementIndex;
}
}
TRangedForConstIterator operator++()
{
// Scan for the next first valid element.
do
{
++ElementIndex;
} while (ElementIndex < Array.GetMaxSize() && !Array.AllocatedElementsBitArray[ElementIndex]);
return *this;
}
bool operator!=(const TRangedForConstIterator& Other) const
{
return ElementIndex != Other.ElementIndex;
}
const ElementType& operator*() const
{
return Array[ElementIndex];
}
private:
const TChunkedSparseArray<ElementType>& Array;
int32 ElementIndex;
};
// Iterate over all allocated elements (skip free ones)
TRangedForIterator begin() { return TRangedForIterator(*this, 0); }
TRangedForIterator end() { return TRangedForIterator(*this, GetMaxSize()); }
TRangedForConstIterator begin() const { return TRangedForConstIterator(*this, 0); }
TRangedForConstIterator end() const { return TRangedForConstIterator(*this, GetMaxSize()); }
private:
static constexpr int32 ElementsPerChunk = BytesPerChunk / sizeof(ElementType);
int32 FreeElementIndexHint = 0;
int32 NumAllocatedElements = 0;
int32 MaxAllocatedElementIndexPlusOne = 0;
TArray<ElementType*> ElementChunks;
TBitArray<> AllocatedElementsBitArray;
};