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

1237 lines
41 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreTypes.h"
#include "Containers/BinaryHeap.h"
#include "Containers/SparseArray.h"
#include "Experimental/Containers/RobinHoodHashTable.h"
#include "Lumen/Lumen.h"
#include "Lumen/LumenHeightfields.h"
#include "Lumen/LumenSparseSpanArray.h"
#include "Lumen/LumenSceneGPUDrivenUpdate.h"
#include "Lumen/LumenSurfaceCacheFeedback.h"
#include "Lumen/LumenUniqueList.h"
#include "LumenDefinitions.h"
#include "MeshCardRepresentation.h"
#include "RenderTransform.h"
#include "ShaderParameterMacros.h"
#include "UnifiedBuffer.h"
#include "Tasks/Task.h"
#include "Math/DoubleFloat.h"
enum class ELumenReflectionPass
{
Opaque,
SingleLayerWater,
FrontLayerTranslucency,
MAX
};
class FDistanceFieldSceneData;
class FLumenCardBuildData;
class FLumenCardPassUniformParameters;
class FLumenCardRenderer;
class FLumenMeshCards;
class FLumenViewState;
class FMeshCardsBuildData;
class FPrimitiveSceneInfo;
class FViewUniformShaderParameters;
class FRDGScatterUploadBuilder;
struct FLumenPageTableEntry;
BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FLumenCardScene, )
SHADER_PARAMETER(uint32, NumCards)
SHADER_PARAMETER(uint32, NumMeshCards)
SHADER_PARAMETER(uint32, NumCardPages)
SHADER_PARAMETER(uint32, NumHeightfields)
SHADER_PARAMETER(uint32, NumPrimitiveGroups)
SHADER_PARAMETER(FVector2f, PhysicalAtlasSize)
SHADER_PARAMETER(FVector2f, InvPhysicalAtlasSize)
SHADER_PARAMETER(float, IndirectLightingAtlasDownsampleFactor)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<float4>, CardData)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<float4>, CardPageData)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<float4>, MeshCardsData)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<float4>, HeightfieldData)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<float4>, PrimitiveGroupData)
SHADER_PARAMETER_RDG_BUFFER_SRV(ByteAddressBuffer, PageTableBuffer)
SHADER_PARAMETER_RDG_BUFFER_SRV(ByteAddressBuffer, SceneInstanceIndexToMeshCardsIndexBuffer)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, AlbedoAtlas)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, OpacityAtlas)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, NormalAtlas)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, EmissiveAtlas)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, DepthAtlas)
END_GLOBAL_SHADER_PARAMETER_STRUCT()
BEGIN_SHADER_PARAMETER_STRUCT(FLumenLandscapeHeightSamplingParameters, )
SHADER_PARAMETER_SRV(Texture2D, HeightVirtualTexture)
SHADER_PARAMETER_TEXTURE(Texture2D<uint4>, HeightVirtualTexturePageTable)
SHADER_PARAMETER_TEXTURE(Texture2D<uint>, HeightVirtualTexturePageTableIndirection)
SHADER_PARAMETER(uint32, HeightVirtualTextureAdaptive)
SHADER_PARAMETER_SAMPLER(SamplerState, HeightVirtualTextureSampler)
SHADER_PARAMETER(FVector3f, HeightVirtualTextureLWCTile)
SHADER_PARAMETER(FMatrix44f, HeightVirtualTextureWorldToUVTransform)
SHADER_PARAMETER(uint32, HeightVirtualTextureEnabled)
SHADER_PARAMETER(FUintVector4, HeightVirtualTexturePackedUniform0)
SHADER_PARAMETER(FUintVector4, HeightVirtualTexturePackedUniform1)
SHADER_PARAMETER(FUintVector4, HeightVirtualTextureUniforms)
END_SHADER_PARAMETER_STRUCT()
namespace Lumen
{
constexpr uint32 FeedbackBufferElementStride = 2;
uint32 GetFeedbackBufferSize(const FViewFamilyInfo& ViewFamily);
uint32 GetCompactedFeedbackBufferSize();
bool SetLandscapeHeightSamplingParameters(const FVector& LumenSceneViewOrigin, const FScene* Scene, FLumenLandscapeHeightSamplingParameters& OutParameters);
};
union FLumenCardId
{
static constexpr uint64 InvalidPackedValue = -1;
uint64 PackedValue;
struct
{
uint32 ResLevelBiasX : 4;
uint32 ResLevelBiasY : 4;
uint32 AxisAlignedDirectionIndex : 3;
uint32 Unused : 21;
uint32 CustomId;
};
FLumenCardId() = default;
FLumenCardId(uint32 InCustomId, uint8 InAxisAlignedDirectionIndex, uint8 InResLevelBiasX, uint8 InResLevelBiasY)
{
if (InCustomId != UINT32_MAX)
{
check(InAxisAlignedDirectionIndex <= 7u && InResLevelBiasX <= 15u && InResLevelBiasY <= 15u);
CustomId = InCustomId;
Unused = 0;
AxisAlignedDirectionIndex = InAxisAlignedDirectionIndex;
ResLevelBiasX = InResLevelBiasX;
ResLevelBiasY = InResLevelBiasY;
}
else
{
Invalidate();
}
}
bool operator<(const FLumenCardId& Other) const
{
return PackedValue < Other.PackedValue;
}
bool operator<=(const FLumenCardId& Other) const
{
return PackedValue <= Other.PackedValue;
}
bool operator==(const FLumenCardId& Other) const
{
return PackedValue == Other.PackedValue;
}
bool operator!=(const FLumenCardId& Other) const
{
return PackedValue != Other.PackedValue;
}
bool IsValid() const
{
return PackedValue != InvalidPackedValue;
}
void Invalidate()
{
PackedValue = InvalidPackedValue;
}
static constexpr FLumenCardId GetInvalidId()
{
FLumenCardId Id;
Id.PackedValue = InvalidPackedValue;
return Id;
}
};
static_assert(sizeof(FLumenCardId) == sizeof(uint64), "Unexpected size of FLumenCardId");
inline uint32 GetTypeHash(const FLumenCardId& Key)
{
return GetTypeHash(Key.PackedValue);
}
struct FLumenCardSharingInfo
{
uint32 CardIndex : 27;
uint32 MinAllocatedResLevel : 4;
uint32 bAxisXFlipped : 1;
FLumenCardSharingInfo() = default;
FLumenCardSharingInfo(uint32 InCardIndex, uint8 InMinAllocatedResLevel, bool bInAxisXFlipped)
: CardIndex(InCardIndex)
, MinAllocatedResLevel(InMinAllocatedResLevel)
, bAxisXFlipped(bInAxisXFlipped)
{}
};
struct FLumenCardSharingInfoPendingRemove
{
FLumenCardId CardId;
int32 CardSharingListIndex;
FLumenCardSharingInfoPendingRemove() = default;
FLumenCardSharingInfoPendingRemove(FLumenCardId InCardId, int32 InCardSharingListIndex)
: CardId(InCardId)
, CardSharingListIndex(InCardSharingListIndex)
{}
bool operator<(const FLumenCardSharingInfoPendingRemove& Other) const
{
if (CardId == Other.CardId)
{
return CardSharingListIndex < Other.CardSharingListIndex;
}
return CardId < Other.CardId;
}
};
struct FLumenCardSharingInfoPendingAdd
{
FLumenCardId CardId;
int32 CardIndex;
uint8 MinAllocatedResLevel;
bool bAxisXFlipped;
FLumenCardSharingInfoPendingAdd() = default;
FLumenCardSharingInfoPendingAdd(FLumenCardId InCardId, int32 InCardIndex, uint8 InMinAllocatedResLevel, bool bInAxisXFlipped)
: CardId(InCardId)
, CardIndex(InCardIndex)
, MinAllocatedResLevel(InMinAllocatedResLevel)
, bAxisXFlipped(bInAxisXFlipped)
{}
bool operator<(const FLumenCardSharingInfoPendingAdd& Other) const
{
if (CardId == Other.CardId)
{
if (MinAllocatedResLevel == Other.MinAllocatedResLevel)
{
return CardIndex < Other.CardIndex;
}
return MinAllocatedResLevel > Other.MinAllocatedResLevel;
}
return CardId < Other.CardId;
}
};
struct FLumenSurfaceMipMap
{
uint8 SizeInPagesX = 0;
uint8 SizeInPagesY = 0;
uint8 ResLevelX = 0;
uint8 ResLevelY = 0;
int32 PageTableSpanOffset = -1;
uint16 PageTableSpanSize = 0;
bool bLocked = false;
bool IsAllocated() const
{
return PageTableSpanSize > 0;
}
FIntPoint GetSizeInPages() const
{
return FIntPoint(SizeInPagesX, SizeInPagesY);
}
int32 GetPageTableIndex(int32 LocalPageIndex) const
{
return PageTableSpanOffset + LocalPageIndex;
}
};
struct FLumenMipMapDesc
{
FIntPoint Resolution;
FIntPoint SizeInPages;
FIntPoint PageResolution;
uint16 ResLevelX;
uint16 ResLevelY;
bool bSubAllocation;
};
class FLumenCard
{
public:
FLumenCard();
~FLumenCard();
FLumenCardOBBf LocalOBB;
FLumenCardOBBd WorldOBB;
FLumenCardOBBf MeshCardsOBB;
bool bVisible = false;
bool bHeightfield = false;
bool bAxisXFlipped = false;
ELumenCardDilationMode DilationMode = ELumenCardDilationMode::Disabled;
// First and last allocated mip map
uint8 MinAllocatedResLevel = UINT8_MAX;
uint8 MaxAllocatedResLevel = 0;
// Requested res level based on distance. Actual allocated res level may be lower if atlas is out of space.
uint8 DesiredLockedResLevel = 0;
// Surface cache allocations per mip map, indexed by [ResLevel - Lumen::MinResLevel]
FLumenSurfaceMipMap SurfaceMipMaps[Lumen::NumResLevels];
int32 MeshCardsIndex = -1;
int32 IndexInMeshCards = -1;
uint8 IndexInBuildData = UINT8_MAX;
uint8 AxisAlignedDirectionIndex = UINT8_MAX;
float ResolutionScale = 1.0f;
// Initial WorldOBB.Extent.X / WorldOBB.Extent.Y, which can't change during reallocation
float CardAspect = 1.0f;
FLumenCardId CardSharingId = FLumenCardId::GetInvalidId();
int32 CardSharingListIndex = INDEX_NONE;
void Initialize(
float InResolutionScale,
uint32 CustomId,
const FMatrix& LocalToWorld,
const FLumenMeshCards& InMeshCardsInstance,
const FLumenCardBuildData& CardBuildData,
int32 InIndexInMeshCards,
int32 InMeshCardsIndex,
uint8 InIndexInBuildData);
void SetTransform(const FMatrix& LocalToWorld, const FLumenMeshCards& MeshCards);
void UpdateMinMaxAllocatedLevel();
bool IsAllocated() const
{
return MinAllocatedResLevel <= MaxAllocatedResLevel;
}
struct FSurfaceStats
{
uint32 NumVirtualTexels = 0;
uint32 NumLockedVirtualTexels = 0;
uint32 NumPhysicalTexels = 0;
uint32 NumLockedPhysicalTexels = 0;
uint32 DroppedResLevels = 0;
};
void GetSurfaceStats(const TSparseSpanArray<FLumenPageTableEntry>& PageTable, FSurfaceStats& Stats) const;
FLumenSurfaceMipMap& GetMipMap(int32 ResLevel)
{
const int32 MipIndex = ResLevel - Lumen::MinResLevel;
check(MipIndex >= 0 && MipIndex < UE_ARRAY_COUNT(SurfaceMipMaps));
return SurfaceMipMaps[MipIndex];
}
FIntPoint ResLevelToResLevelXYBias() const;
void GetMipMapDesc(int32 ResLevel, FLumenMipMapDesc& Desc) const;
const FLumenSurfaceMipMap& GetMipMap(int32 ResLevel) const
{
const int32 MipIndex = ResLevel - Lumen::MinResLevel;
check(MipIndex >= 0 && MipIndex < UE_ARRAY_COUNT(SurfaceMipMaps));
return SurfaceMipMaps[MipIndex];
}
};
class FLumenPrimitiveGroupRemoveInfo
{
public:
FLumenPrimitiveGroupRemoveInfo(const FPrimitiveSceneInfo* InPrimitive, int32 InPrimitiveIndex);
// Must not be dereferenced after creation, the primitive was removed from the scene and deleted
// Value of the pointer is still useful for map lookups
const FPrimitiveSceneInfo* Primitive;
// Need to copy by value as this is a deferred remove and Primitive may be already destroyed
int32 PrimitiveIndex;
TArray<int32, TInlineAllocator<1>> LumenPrimitiveGroupIndices;
};
// Defines a group of scene primitives for a given LOD level
class FLumenPrimitiveGroup
{
public:
TArray<FPrimitiveSceneInfo*, TInlineAllocator<1>> Primitives;
int32 PrimitiveInstanceIndex = -1;
int32 MeshCardsIndex = -1;
int32 HeightfieldIndex = -1;
int32 PrimitiveCullingInfoIndex = INDEX_NONE;
int32 InstanceCullingInfoIndex = INDEX_NONE;
uint32 CustomId = UINT32_MAX;
Experimental::FHashElementId RayTracingGroupMapElementId;
float CardResolutionScale = 1.0f;
bool bValidMeshCards = false;
bool bFarField = false;
bool bHeightfield = false;
bool bEmissiveLightSource = false;
bool bOpaqueOrMasked = true;
uint32 LightingChannelMask = UINT32_MAX;
bool HasMergedInstances() const;
bool HasMergedPrimitives() const
{
return RayTracingGroupMapElementId.IsValid();
}
};
struct FLumenPrimitiveGroupCullingInfo
{
uint32 bVisible : 1;
uint32 bValidMeshCards : 1;
uint32 bFarField : 1;
uint32 bEmissiveLightSource : 1;
uint32 bOpaqueOrMasked : 1;
uint32 NumInstances : 27;
union
{
int32 PrimitiveGroupIndex;
int32 InstanceCullingInfoOffset;
};
FRenderBounds WorldSpaceBoundingBox; // LWC_TODO
FLumenPrimitiveGroupCullingInfo() = default;
FLumenPrimitiveGroupCullingInfo(const FRenderBounds& Bounds, const FLumenPrimitiveGroup& PrimitiveGroup, int32 InPrimitiveGroupIndex)
: bVisible(false)
, bValidMeshCards(PrimitiveGroup.bValidMeshCards)
, bFarField(PrimitiveGroup.bFarField)
, bEmissiveLightSource(PrimitiveGroup.bEmissiveLightSource)
, bOpaqueOrMasked(PrimitiveGroup.bOpaqueOrMasked)
, NumInstances(0)
, PrimitiveGroupIndex(InPrimitiveGroupIndex)
, WorldSpaceBoundingBox(Bounds)
{}
FLumenPrimitiveGroupCullingInfo(const FRenderBounds& Bounds, int32 InInstanceCullingInfoOffset, uint32 InNumInstances, bool bInFarField)
: bVisible(false)
, bValidMeshCards(false)
, bFarField(bInFarField)
, bEmissiveLightSource(false)
, bOpaqueOrMasked(false)
, NumInstances(InNumInstances)
, InstanceCullingInfoOffset(InInstanceCullingInfoOffset)
, WorldSpaceBoundingBox(Bounds)
{}
};
struct FLumenPageTableEntry
{
// Allocated physical page data
FIntPoint PhysicalPageCoord = FIntPoint(-1, -1);
// Allows to point to a sub-allocation inside a shared physical page
FIntRect PhysicalAtlasRect;
// Sampling data, can point to a coarser page
uint32 SamplePageIndex = 0;
uint16 SampleAtlasBiasX = 0;
uint16 SampleAtlasBiasY = 0;
uint16 SampleCardResLevelX = 0;
uint16 SampleCardResLevelY = 0;
// CardPage for atlas operations
int32 CardIndex = -1;
uint8 ResLevel = 0;
FVector4f CardUVRect;
FIntPoint SubAllocationSize = FIntPoint(-1, -1);
bool IsSubAllocation() const
{
return SubAllocationSize.X >= 0 || SubAllocationSize.Y >= 0;
}
bool IsMapped() const
{
return PhysicalPageCoord.X >= 0 && PhysicalPageCoord.Y >= 0;
}
uint32 GetNumVirtualTexels() const
{
return IsSubAllocation() ? SubAllocationSize.X * SubAllocationSize.Y : Lumen::VirtualPageSize * Lumen::VirtualPageSize;
}
uint32 GetNumPhysicalTexels() const
{
return IsMapped() ? PhysicalAtlasRect.Area() : 0;
}
};
class FSurfaceCacheRequest
{
public:
int32 CardIndex = -1;
uint16 ResLevel = 0;
uint16 LocalPageIndex = UINT16_MAX;
float Distance = 0.0f;
bool IsLockedMip() const { return LocalPageIndex == UINT16_MAX; }
};
union FVirtualPageIndex
{
FVirtualPageIndex() {}
FVirtualPageIndex(int32 InCardIndex, uint16 InResLevel, uint16 InLocalPageIndex)
: CardIndex(InCardIndex), ResLevel(InResLevel), LocalPageIndex(InLocalPageIndex)
{}
uint64 PackedValue;
struct
{
int32 CardIndex;
uint16 ResLevel;
uint16 LocalPageIndex;
};
};
// Physical page allocator, which routes sub page sized allocations to a bin allocator
class FLumenSurfaceCacheAllocator
{
public:
struct FAllocation
{
// Allocated physical page data
FIntPoint PhysicalPageCoord = FIntPoint(-1, -1);
// Allows to point to a sub-allocation inside a shared physical page
FIntRect PhysicalAtlasRect;
};
struct FBinStats
{
FIntPoint ElementSize = FIntPoint(0, 0);
int32 NumAllocations = 0;
int32 NumPages = 0;
};
struct FStats
{
uint32 NumFreePages = 0;
uint32 BinNumPages = 0;
uint32 BinNumWastedPages = 0;
uint32 BinPageFreeTexels = 0;
TArray<FBinStats> Bins;
};
void Init(const FIntPoint& InPageAtlasSizeInPages);
void Allocate(const FLumenPageTableEntry& Page, FAllocation& Allocation);
void Free(const FLumenPageTableEntry& Page);
bool IsSpaceAvailable(const FLumenCard& Card, int32 ResLevel, bool bSinglePage) const;
void GetStats(FStats& Stats) const;
private:
// Data structure overview
// -----------------------
// * The atlas is divided in to pages
// * Each page is 128x128
// * Each page can be divided into sub-allocation to hold smaller element size (e..g 8x8, 8x16, 8x32, ...)
//
// * Card elements are allocated into these pages.
// * Card elements are allocated into page with the correct sub-allocation size
//
// Data structures:
// * FPageBin - Holds reference to all the page allocations for a given element size. Due to this, there is at max. 64 FPageBin E.g.:
// * 1 FPageBin for 8x8 allocation,
// * 1 FPageBin for 8x16 allocation,
// * 1 FPageBin for 8x32 allocation,
// * ...
// A PageBin holds reference to several PageBinAllocation, one per physical page. There can be a large quantity of FPageBinAllocation
// * FPageBinAllocation - Tracks the sub-allocation within a single physical page. A physical page (128x128) will be broken into 32x32 sub-allocation for
// an element size of 8x8. The sub-allocation is tracked with a bitfield to indicate which slot is available or not
// * FPageBinLookup - Lookup table to fast retrivable of FPageBin based on the desired element size. The lookup is a 8x8 table, so there is at max 64 FPageBin
struct FPageBinAllocation
{
public:
void Init(const FIntPoint& InPageCoord, const FIntPoint& InPageSizeInElements)
{
static_assert(Lumen::MinResLevel == 3);
static_assert(Lumen::PhysicalPageSize == 128);
PageCoord = InPageCoord;
PageSizeInElements = InPageSizeInElements;
SubPageFreeCount = InPageSizeInElements.X * InPageSizeInElements.Y;
SubPageList.SetNum(SubPageFreeCount, false);
}
FIntPoint Add()
{
const int32 Index = SubPageList.FindAndSetFirstZeroBit();
checkSlow(Index != INDEX_NONE);
--SubPageFreeCount;
return FIntPoint(Index % PageSizeInElements.X, Index / PageSizeInElements.X);
}
void Remove(const FIntPoint& In)
{
const int32 Index = In.X + PageSizeInElements.X * In.Y;
checkSlow(SubPageList.IsValidIndex(Index));
++SubPageFreeCount;
SubPageList[Index] = false;
}
uint32 GetSubPageFreeCount() const
{
return SubPageFreeCount;
}
bool HasFreeElements() const
{
return SubPageFreeCount > 0;
}
bool IsEmpty() const
{
return SubPageFreeCount == PageSizeInElements.X * PageSizeInElements.Y;
}
FIntPoint PageCoord;
FIntPoint PageSizeInElements;
private:
// 256 bits for storing sub-page elements
// * MinPage size is 2^Lumen::MinResLevel=8
// * Physical page is Lumen::PhysicalPageSize=128
// * Max sub-allocation within a physical page is (128/8)^2 = 16x16 = 256
// Values -> 0:free 1:used
TBitArray<TInlineAllocator<8>> SubPageList;
int32 SubPageFreeCount = 0;
};
// There is only a single FPageBin per element size (8x8, 8x16, 8x32, 8x64, 128x64)
// At max there should be 64 FPageBin elements
struct FPageBin
{
FPageBin(const FIntPoint& InElementSize);
int32 GetSubPageCount() const
{
return PageSizeInElements.X * PageSizeInElements.Y;
}
uint32 GetBinAllocationCount() const
{
return BinAllocations.Num();
}
uint32 GetSubPageFreeCount() const
{
uint32 Count = 0;
for (auto& BinAllocation : BinAllocations)
{
Count += BinAllocation.GetSubPageFreeCount();
}
return Count;
}
bool HasFreeElements() const
{
// Ideally, make a 0(1) lookup for this
for (auto& BinAllocation : BinAllocations)
{
if (BinAllocation.HasFreeElements())
{
return true;
}
}
return false;
}
FPageBinAllocation* GetBinAllocation()
{
// Ideally, make a 0(1) lookup for this
for (auto& BinAllocation : BinAllocations)
{
if (BinAllocation.HasFreeElements())
{
return &BinAllocation;
}
}
return nullptr;
}
FPageBinAllocation* AddBinAllocation(const FIntPoint& InPageCoord)
{
FPageBinAllocation& NewBinAllocation = BinAllocations.AddDefaulted_GetRef();
NewBinAllocation.Init(InPageCoord, PageSizeInElements);
return &NewBinAllocation;
}
// Return true if the bin is now completely empty (and can be deleted), false otherwise.
bool RemoveBinAllocation(const FLumenPageTableEntry& Page)
{
// Ideally, make a 0(1) lookup for this
for (uint32 BinAllocIt = 0, BinAllocCount = BinAllocations.Num(); BinAllocIt < BinAllocCount; ++BinAllocIt)
{
FPageBinAllocation& BinAllocation = BinAllocations[BinAllocIt];
if (BinAllocation.PageCoord == Page.PhysicalPageCoord)
{
const FIntPoint ElementCoord = (Page.PhysicalAtlasRect.Min - BinAllocation.PageCoord * Lumen::PhysicalPageSize) / ElementSize;
BinAllocation.Remove(ElementCoord);
const bool bIsEmpty = BinAllocation.IsEmpty();
if (bIsEmpty)
{
BinAllocations.RemoveAtSwap(BinAllocIt);
}
return bIsEmpty;
}
}
check(false); // Shouldn't reach this
return false;
}
FIntPoint ElementSize = FIntPoint(0, 0);
FIntPoint PageSizeInElements = FIntPoint(0, 0);
private:
TArray<FPageBinAllocation, TInlineAllocator<16>> BinAllocations;
};
// Physical pages
FIntPoint AllocatePhysicalAtlasPage();
void FreePhysicalAtlasPage(const FIntPoint& PageCoord);
// Stored into a bitfield (0:free,1:used)
// Mapping from page coord to bit is using simple linear remapping
TBitArray<TInlineAllocator<32>> PhysicalPageList;
int32 PhysicalPageFreeCount = 0;
FIntPoint PageAtlasSizeInPages = FIntPoint::ZeroValue;
TArray<FPageBin> PageBins;
// Bin lookups are stored as 2D mapping (8x8 - [1-128]x[1-128])
// This mapping indexes PageX dim. and PageY dim.
// As an example, a 8x16 SubPage allocator will be stored at [3,4] (i.e., [log2(8),log2(16)] )
// 0 1 2 3 4 5 6 7
// --------------------
// 1 2 4 8 16 32 64 128
// 0 | 1
// 1 | 2 X
// 2 | 4
// 3 | 8 X
// 4 | 16
// 5 | 32 X
// 6 | 64 X
// 7 | 128
static const uint8 InvalidPageBinIndex = 0xFF;
typedef TStaticArray<uint8, 64u> FPageBinLookup;
FPageBinLookup PageBinLookup;
bool bInitPageBinLookup = true;
uint8 GetLookupIndex(const FIntPoint& InRes) const
{
checkSlow(FMath::IsPowerOfTwo(InRes.X) && FMath::IsPowerOfTwo(InRes.Y));
checkSlow(InRes.X <= Lumen::PhysicalPageSize && InRes.Y <= Lumen::PhysicalPageSize);
const uint32 OutIndex = FMath::FloorLog2(InRes.X) + FMath::FloorLog2(InRes.Y) * 8u;
//check(OutIndex < 64u);
return (uint8)OutIndex;
}
const FPageBin* GetBin(const FIntPoint& InRes) const
{
const uint8 LookupIndex = GetLookupIndex(InRes);
const uint8 BinIndex = PageBinLookup[LookupIndex];
if (BinIndex != InvalidPageBinIndex)
{
return &PageBins[BinIndex];
}
return nullptr;
}
FPageBin* GetBin(const FIntPoint& InRes)
{
const uint8 LookupIndex = GetLookupIndex(InRes);
const uint8 BinIndex = PageBinLookup[LookupIndex];
if (BinIndex != InvalidPageBinIndex)
{
return &PageBins[BinIndex];
}
return nullptr;
}
FPageBin* GetOrAddBin(const FIntPoint& InRes)
{
const uint8 LookupIndex = GetLookupIndex(InRes);
const uint8 BinIndex = PageBinLookup[LookupIndex];
if (BinIndex == InvalidPageBinIndex)
{
PageBinLookup[LookupIndex] = PageBins.Num();
PageBins.Add(FPageBin(InRes));
// There can't be more than 64 PageBins, as the sub-allocation resolution within
// a 128x128 physical page is bound to 64 (8x8, 8x16, 8x32, ..., 128x64, 128x128).
check(PageBins.Num() <= 64);
return &PageBins.Last();
}
else
{
return &PageBins[BinIndex];
}
}
};
enum class ESurfaceCacheCompression : uint8
{
Disabled,
UAVAliasing,
CopyTextureRegion,
FramebufferCompression
};
ESurfaceCacheCompression GetSurfaceCacheCompression();
class FLumenSharedRT
{
public:
FRDGTextureRef CreateSharedRT(FRDGBuilder& Builder, const FRDGTextureDesc& Desc, FIntPoint VisibleExtent, const TCHAR* Name, ERDGTextureFlags Flags = ERDGTextureFlags::None);
FRDGTextureRef GetRenderTarget() const
{
return RenderTarget;
}
private:
FRDGTextureRef RenderTarget = nullptr;
};
// Unique view origin. Typically one per view, but for the case of cube captures, a single view origin is shared.
// The advantage of sharing an origin is that Lumen scene data can be shared and updated once. In the future, we could
// allow origins to be shared for other use cases, such as nDisplay inner frustums, or imagine a sim with a wide angle
// view across three monitors, where the three views share an origin.
struct FLumenViewOrigin
{
void Init(const FViewInfo& View);
bool IsPerspectiveProjection() const
{
return OrthoMaxDimension == 0.0f;
}
const FSceneViewFamily* Family;
FVector LumenSceneViewOrigin;
FVector4f WorldCameraOrigin;
FDFVector3 PreViewTranslationDF;
// Matrix used for frustum clipping tests in Lumen. For typical views, this is set to WorldToClip, while cube captures
// have an omnidirectional projection, and use a trivial matrix that will pass any point as in-frustum.
FMatrix44f FrustumTranslatedWorldToClip;
FMatrix44f ViewToClip;
float OrthoMaxDimension; // If orthographic projection, max dimension, otherwise zero
float LastEyeAdaptationExposure; // Shared origin views share exposure
float MaxTraceDistance; // Shared origin views share post process settings, which control these values
float CardMaxDistance;
float LumenSceneDetail;
// Ideally this structure would contain a mirror of all view origin specific data, so Lumen scene updates don't end up with
// dependencies on FViewInfo, but there are still some code paths that pull data from the FViewInfo structure, which are messy
// to refactor. So this reference view is included to allow fetching an FViewInfo to send to those code paths.
//
// The first is the "GetDeferredLightParameters" utility function, which uses a bunch of data from the FViewInfo structure, which
// will be invariant across shared origin views in practice. This includes fields originally copied from CVars, post process
// settings, and projection type. In the future, we could add an API variation that takes those all values as loose parameters.
//
// The View uniform buffer is used to access some data assumed to be invariant for views that share an origin:
// View.StateFrameIndex shared origin views are created on same frame and always render together
// View.StateFrameIndexMod8 ""
// View.PreExposure shared origin views share exposure
// View.OneOverPreExposure ""
//
// The Substrate global uniform buffer is accessed from FViewInfo, but doesn't include any view dependent data. Looking at
// InitialiseSubstrateViewData, it uses SceneTexturesConfig.Extent (as opposed to a view rect), and View.GetShaderPlatform().
// The Substrate uniforms aren't initialized until mid render, while the view origin is created early in render. We could
// copy those into the Lumen view origin later, but it works well enough to grab it from the view when it's needed.
//
// Messier are the uses of FViewInfo in FDeferredShadingSceneRenderer::RenderDirectLightingForLumenScene, where view specific
// forward lighting data, volumetric cloud shadows, ray tracing TLAS, miscellaneous post process settings, shader map, view
// family, feature level, and FScene are referenced. Basically a ton of stuff. To share all that, we probably need to refactor
// things so there is a formal concept of shared origin views (FViewSharedOrigin?) at a higher level in the scene renderer
// itself. Then we could pull all of the above into that structure. But that goes well beyond the scope of adding Lumen support
// for cube maps, which is the immediate goal.
//
// There may be rendering artifacts with forward lighting, volumetric cloud shadows, and ray tracing, given that the code that
// generates those may not be completely shared origin view friendly. Forward lighting pulls in lights from the frustum, so that
// definitely seems like it should be modified to take into account the frustums of all shared origin views. It's less clear if
// volumetric cloud shadows and ray tracing are view direction or just view origin aware (offhand, they look origin aware, but I
// haven't done a deep dive).
//
const FViewInfo* ReferenceView;
};
// Temporaries valid only in a single frame
struct FLumenSceneFrameTemporaries
{
FLumenSceneFrameTemporaries(const TArray<FViewInfo>& Views);
// Current frame's buffers for writing feedback
FLumenSurfaceCacheFeedback::FFeedbackResources SurfaceCacheFeedbackResources;
FRDGTextureRef AlbedoAtlas = nullptr;
FRDGTextureRef OpacityAtlas = nullptr;
FRDGTextureRef NormalAtlas = nullptr;
FRDGTextureRef EmissiveAtlas = nullptr;
FRDGTextureRef DepthAtlas = nullptr;
FRDGTextureRef DirectLightingAtlas = nullptr;
FRDGTextureRef IndirectLightingAtlas = nullptr;
FRDGTextureRef RadiosityNumFramesAccumulatedAtlas = nullptr;
FRDGTextureRef FinalLightingAtlas = nullptr;
FRDGBufferRef TileShadowDownsampleFactorAtlas = nullptr;
FRDGTextureRef DiffuseLightingAndSecondMomentHistoryAtlas = nullptr;
FRDGTextureRef NumFramesAccumulatedHistoryAtlas = nullptr;
FRDGBufferSRV* CardBufferSRV = nullptr;
FRDGBufferSRV* MeshCardsBufferSRV = nullptr;
FRDGBufferSRV* HeightfieldBufferSRV = nullptr;
FRDGBufferSRV* PrimitiveGroupBufferSRV = nullptr;
FRDGBufferSRV* SceneInstanceIndexToMeshCardsIndexBufferSRV = nullptr;
FRDGBufferSRV* PageTableBufferSRV = nullptr;
FRDGBufferSRV* CardPageBufferSRV = nullptr;
FRDGBufferUAV* CardPageBufferUAV = nullptr;
FRDGBufferUAV* CardPageLastUsedBufferUAV = nullptr;
FRDGBufferSRV* CardPageLastUsedBufferSRV = nullptr;
FRDGBufferUAV* CardPageHighResLastUsedBufferUAV = nullptr;
FRDGBufferSRV* CardPageHighResLastUsedBufferSRV = nullptr;
TRDGUniformBufferRef<FLumenCardScene> LumenCardSceneUniformBuffer = nullptr;
FRHIGPUBufferReadback* SceneAddOpsReadbackBuffer = nullptr;
FRHIGPUBufferReadback* SceneRemoveOpsReadbackBuffer = nullptr;
FRHIGPUBufferReadback* SurfaceCacheFeedbackBuffer = nullptr;
UE::Tasks::FTask UpdateSceneTask;
bool bReallocateAtlas = false;
TArray<FLumenViewOrigin, TFixedAllocator<LUMEN_MAX_VIEWS>> ViewOrigins;
FIntPoint ViewExtent;
// Targets shared per view, but can't be shared per pass
FLumenSharedRT ReflectSpecularIndirect[(uint32)ELumenReflectionPass::MAX];
FLumenSharedRT ReflectNumHistoryFrames[(uint32)ELumenReflectionPass::MAX];
FLumenSharedRT ReflectResolveVariance[(uint32)ELumenReflectionPass::MAX];
FLumenSharedRT DiffuseIndirect;
FLumenSharedRT LightIsMoving;
FLumenSharedRT BackfaceDiffuseIndirect;
FLumenSharedRT RoughSpecularIndirect;
FLumenSharedRT ResolveVariance;
FLumenSharedRT NewDiffuseIndirect;
FLumenSharedRT NewBackfaceDiffuseIndirect;
FLumenSharedRT NewRoughSpecularIndirect;
FLumenSharedRT NewHistoryFastUpdateMode_NumFramesAccumulated;
FLumenSharedRT NewResolveVariance;
FLumenSharedRT DepthHistory;
FLumenSharedRT NormalHistory;
FLumenSharedRT ReservoirRayDirection;
FLumenSharedRT ReservoirTraceRadiance;
FLumenSharedRT ReservoirTraceHitDistance;
FLumenSharedRT ReservoirTraceHitNormal;
FLumenSharedRT ReservoirWeights;
FLumenSharedRT DownsampledSceneDepth;
FLumenSharedRT DownsampledWorldNormal;
// Optional debug data enabled with stats visualization
// Contains cursor point cards information
FRDGBufferSRVRef DebugData = nullptr;
};
// Tracks scene-wide lighting state whose changes we should propagate quickly by flushing various lighting caches
class FLumenGlobalLightingState
{
public:
FLinearColor DirectionalLightColor;
FLinearColor SkyLightColor;
bool bDirectionalLightValid;
bool bSkyLightValid;
FLumenGlobalLightingState()
{
DirectionalLightColor = FLinearColor::Black;
SkyLightColor = FLinearColor::Black;
bDirectionalLightValid = false;
bSkyLightValid = false;
}
};
class FLumenSceneData
{
public:
// Clear all cached state like surface cache atlas. Including extra state like final lighting. Used only for debugging.
bool bDebugClearAllCachedState = false;
// Whether we allow sharing cards between primitive groups
bool bAllowCardSharing = false;
// Whether we allow cards to downsample from self when lowering resolutions
bool bAllowCardDownsampleFromSelf = false;
// Whether we should re-upload entire Lumen Scene on next update
bool bReuploadSceneRequest = false;
TSparseSpanArray<FLumenCard> Cards;
FUniqueIndexList CardIndicesToUpdateInBuffer;
TRefCountPtr<FRDGPooledBuffer> CardBuffer;
FRDGAsyncScatterUploadBuffer CardUploadBuffer;
// Primitive groups
FUniqueIndexList PrimitiveGroupIndicesToUpdateInBuffer;
TSparseSpanArray<FLumenPrimitiveGroup> PrimitiveGroups;
TRefCountPtr<FRDGPooledBuffer> PrimitiveGroupBuffer;
FRDGAsyncScatterUploadBuffer PrimitiveGroupUploadBuffer;
// Maps RayTracingGroupId to a specific Primitive Group Index
Experimental::TRobinHoodHashMap<int32, int32> RayTracingGroups;
// List of landscape primitives added to the Lumen scene
TArray<const FPrimitiveSceneInfo*> LandscapePrimitives;
// Mesh Cards
FUniqueIndexList MeshCardsIndicesToUpdateInBuffer;
TSparseSpanArray<FLumenMeshCards> MeshCards;
TSparseSpanArray<FLumenPrimitiveGroupCullingInfo> InstanceCullingInfos;
TSparseArray<FLumenPrimitiveGroupCullingInfo> PrimitiveCullingInfos;
TRefCountPtr<FRDGPooledBuffer> MeshCardsBuffer;
FRDGAsyncScatterUploadBuffer MeshCardsUploadBuffer;
// Heightfields
FUniqueIndexList HeightfieldIndicesToUpdateInBuffer;
TSparseSpanArray<FLumenHeightfield> Heightfields;
TRefCountPtr<FRDGPooledBuffer> HeightfieldBuffer;
FRDGAsyncScatterUploadBuffer HeightfieldUploadBuffer;
// Page Table
TSparseSpanArray<FLumenPageTableEntry> PageTable;
TArray<int32> PageTableIndicesToUpdateInBuffer;
TRefCountPtr<FRDGPooledBuffer> PageTableBuffer;
FRDGAsyncScatterUploadBuffer PageTableUploadBuffer;
// GPUScene instance index to MeshCards mapping
FUniqueIndexList PrimitivesToUpdateMeshCards;
TRefCountPtr<FRDGPooledBuffer> SceneInstanceIndexToMeshCardsIndexBuffer;
FRDGAsyncScatterUploadBuffer SceneInstanceIndexToMeshCardsIndexUploadBuffer;
// Single card tile per FLumenPageTableEntry. Used for various atlas update operations
TRefCountPtr<FRDGPooledBuffer> CardPageBuffer;
FRDGAsyncScatterUploadBuffer CardPageUploadBuffer;
// Last frame index when this page was sampled from. Used to controlling page update rate
TRefCountPtr<FRDGPooledBuffer> CardPageLastUsedBuffer;
TRefCountPtr<FRDGPooledBuffer> CardPageHighResLastUsedBuffer;
// Captured from the triangle scene
TRefCountPtr<IPooledRenderTarget> AlbedoAtlas;
TRefCountPtr<IPooledRenderTarget> OpacityAtlas;
TRefCountPtr<IPooledRenderTarget> NormalAtlas;
TRefCountPtr<IPooledRenderTarget> EmissiveAtlas;
TRefCountPtr<IPooledRenderTarget> DepthAtlas;
// Generated
TRefCountPtr<IPooledRenderTarget> DirectLightingAtlas;
TRefCountPtr<IPooledRenderTarget> IndirectLightingAtlas;
TRefCountPtr<IPooledRenderTarget> RadiosityNumFramesAccumulatedAtlas;
TRefCountPtr<IPooledRenderTarget> FinalLightingAtlas;
TRefCountPtr<FRDGPooledBuffer> TileShadowDownsampleFactorAtlas;
// Radiosity probes
TRefCountPtr<IPooledRenderTarget> RadiosityTraceRadianceAtlas;
TRefCountPtr<IPooledRenderTarget> RadiosityTraceHitDistanceAtlas;
TRefCountPtr<IPooledRenderTarget> RadiosityProbeSHRedAtlas;
TRefCountPtr<IPooledRenderTarget> RadiosityProbeSHGreenAtlas;
TRefCountPtr<IPooledRenderTarget> RadiosityProbeSHBlueAtlas;
// Direct lighting denoising
TRefCountPtr<IPooledRenderTarget> DiffuseLightingAndSecondMomentHistoryAtlas;
TRefCountPtr<IPooledRenderTarget> NumFramesAccumulatedHistoryAtlas;
// Lumen Scene readback for handling GPU driven updates
FLumenSceneReadback SceneReadback;
// Virtual surface cache feedback
FLumenSurfaceCacheFeedback SurfaceCacheFeedback;
FLumenGlobalLightingState GlobalLightingState;
bool bFinalLightingAtlasContentsValid;
int32 NumMeshCardsToAdd = 0;
int32 NumLockedCardsToUpdate = 0;
int32 NumHiResPagesToAdd = 0;
bool bTrackAllPrimitives;
TSet<FPrimitiveSceneInfo*> PendingAddOperations;
TSet<FPrimitiveSceneInfo*> PendingUpdateOperations;
TSet<FPrimitiveSceneInfo*> PendingSurfaceCacheInvalidationOperations;
TArray<FLumenPrimitiveGroupRemoveInfo> PendingRemoveOperations;
// Scale factor to adjust atlas size for tuning memory usage
float SurfaceCacheResolution = 1.0f;
// Multi-view multi-GPU information
bool bViewSpecific = false;
#if WITH_MGPU
bool bViewSpecificMaskInitialized = false;
FRHIGPUMask ViewSpecificMask;
#endif
FLumenSceneData(EShaderPlatform ShaderPlatform, EWorldType::Type WorldType);
FLumenSceneData(bool bInTrackAllPrimitives);
~FLumenSceneData();
void UpdatePrimitiveInstanceOffset(int32 PrimitiveIndex);
void ResetAndConsolidate();
void AddMeshCards(int32 PrimitiveGroupIndex);
void UpdateMeshCards(const FMatrix& LocalToWorld, int32 MeshCardsIndex, const FMeshCardsBuildData& MeshCardsBuildData);
void InvalidateSurfaceCache(FRHIGPUMask GPUMask, int32 MeshCardsIndex);
void RemoveMeshCards(int32 PrimitiveGroupIndex, bool bUpdateCullingInfo = true);
void RemoveCardFromAtlas(int32 CardIndex);
bool HasPendingOperations() const
{
return PendingAddOperations.Num() > 0 || PendingUpdateOperations.Num() > 0 || PendingRemoveOperations.Num() > 0;
}
void DumpStats(const FDistanceFieldSceneData& DistanceFieldSceneData, bool bDumpMeshDistanceFields, bool bDumpPrimitiveGroups);
bool UpdateAtlasSize();
void ReleaseAtlas();
void RemoveAllMeshCards();
void UploadPageTable(FRDGBuilder& GraphBuilder, FRDGScatterUploadBuilder& UploadBuilder, FLumenSceneFrameTemporaries& FrameTemporaries);
void FillFrameTemporaries(FRDGBuilder& GraphBuilder, FLumenSceneFrameTemporaries& FrameTemporaries);
void AllocateCardAtlases(FRDGBuilder& GraphBuilder, FLumenSceneFrameTemporaries& FrameTemporaries, const FSceneViewFamily* ViewFamily);
void ReallocVirtualSurface(FLumenCard& Card, int32 CardIndex, int32 ResLevel, bool bLockPages);
void FreeVirtualSurface(FLumenCard& Card, uint8 FromResLevel, uint8 ToResLevel);
void UpdateCardMipMapHierarchy(FLumenCard& Card);
bool IsPhysicalSpaceAvailable(const FLumenCard& Card, int32 ResLevel, bool bSinglePage) const
{
return SurfaceCacheAllocator.IsSpaceAvailable(Card, ResLevel, bSinglePage);
}
void ForceEvictEntireCache();
bool EvictOldestAllocation(uint32 MaxFramesSinceLastUsed, TSparseUniqueList<int32, SceneRenderingAllocator>& DirtyCards);
uint32 GetSurfaceCacheUpdateFrameIndex() const;
void IncrementSurfaceCacheUpdateFrameIndex();
const FLumenPageTableEntry& GetPageTableEntry(int32 PageTableIndex) const { return PageTable[PageTableIndex]; }
FLumenPageTableEntry& GetPageTableEntry(int32 PageTableIndex) { return PageTable[PageTableIndex]; }
void MapSurfaceCachePage(const FLumenSurfaceMipMap& MipMap, int32 PageTableIndex, FRHIGPUMask GPUMask);
int32 GetNumCardPages() const { return PageTable.Num(); }
FIntPoint GetPhysicalAtlasSize() const { return PhysicalAtlasSize; }
FIntPoint GetRadiosityAtlasSize() const;
FIntPoint GetCardCaptureAtlasSizeInPages() const;
FIntPoint GetCardCaptureAtlasSize() const;
uint32 GetCardCaptureRefreshNumTexels() const;
uint32 GetCardCaptureRefreshNumPages() const;
ESurfaceCacheCompression GetPhysicalAtlasCompression() const { return PhysicalAtlasCompression; }
struct FFeedbackData
{
const uint32* Data = nullptr;
uint32 NumElements = 0;
};
void UpdateSurfaceCacheFeedback(FFeedbackData Data, const TArray<FVector, TInlineAllocator<2>>& LumenSceneCameraOrigins, TArray<FSurfaceCacheRequest>& MeshCardsUpdate, const FViewFamilyInfo& ViewFamily, int32 RequestHistogram[Lumen::NumDistanceBuckets]);
void ProcessLumenSurfaceCacheRequests(
const FViewInfo& MainView,
float MaxCardUpdateDistanceFromCamera,
int32 MaxTileCapturesPerFrame,
FLumenCardRenderer& LumenCardRenderer,
FRHIGPUMask GPUMask,
const TArray<FSurfaceCacheRequest, SceneRenderingAllocator>& SurfaceCacheRequests);
int32 GetMeshCardsIndex(const FPrimitiveSceneInfo* PrimitiveSceneInfo, int32 InstanceIndex) const;
FLumenPrimitiveGroupCullingInfo& GetPrimitiveGroupCullingInfo(const FLumenPrimitiveGroup& PrimitiveGroup, bool bForcePrimitiveLevel = false);
const FLumenPrimitiveGroupCullingInfo& GetPrimitiveGroupCullingInfo(const FLumenPrimitiveGroup& PrimitiveGroup, bool bForcePrimitiveLevel = false) const;
void RemovePrimitiveGroupCullingInfo(FLumenPrimitiveGroup& PrimitiveGroup);
void UpdatePrimitiveGroupCullingInfo(const FLumenPrimitiveGroup& PrimitiveGroup, const FRenderBounds& NewWorldBounds, bool bForcePrimitiveLevel = false);
// Copy initial data from default Lumen scene data to a view specific Lumen scene data
void CopyInitialData(const FLumenSceneData& SourceSceneData);
#if WITH_MGPU
void UpdateGPUMask(FRDGBuilder& GraphBuilder, const FLumenSceneFrameTemporaries& FrameTemporaries, FLumenViewState& LumenViewState, FRHIGPUMask ViewGPUMask);
#endif
uint64 GetGPUSizeBytes(bool bLogSizes) const;
private:
void AddMeshCardsFromBuildData(int32 PrimitiveGroupIndex, const FMatrix& LocalToWorld, const FMeshCardsBuildData& MeshCardsBuildData, FLumenPrimitiveGroup& PrimitiveGroup);
void UnmapSurfaceCachePage(bool bLocked, FLumenPageTableEntry& Page, int32 PageIndex);
bool RecaptureCardPage(const FViewInfo& MainView, FLumenCardRenderer& LumenCardRenderer, FLumenSurfaceCacheAllocator& CaptureAtlasAllocator, FRHIGPUMask GPUMask, int32 PageTableIndex);
// Try to find a card with the same ID and has a MinAllocatedResLevel >= ResLevel
const FLumenCardSharingInfo* FindMatchingCardForCopy(const FLumenCardId& CardId, uint32 ResLevel) const;
// Called per frame right after we are done querying CardSharingInfoMap
void FlushPendingCardSharingInfos();
// Frame index used to time-splice various surface cache update operations
// 0 is a special value, and means that surface contains default data
uint32 SurfaceCacheUpdateFrameIndex = 1;
// Used to detect change in data format
EPixelFormat CurrentLightingDataFormat = PF_Unknown;
float CurrentCachedLightingPreExposure = 0.0f;
// Virtual surface cache page table
FIntPoint PhysicalAtlasSize = FIntPoint(0, 0);
ESurfaceCacheCompression PhysicalAtlasCompression;
FLumenSurfaceCacheAllocator SurfaceCacheAllocator;
// List of high res allocated physical pages which can be deallocated on demand, ordered by last used frame
// FeedbackFrameIndex, PageTableIndex
FBinaryHeap<uint32, uint32> UnlockedAllocationHeap;
// List of pages for forced recapture, ordered by request frame index
// RequestSurfaceCacheFrameIndex, PageTableIndex
FBinaryHeap<uint32, uint32> PagesToRecaptureHeap[MAX_NUM_GPUS];
// List of pages ordered by last captured frame used to periodically recapture pages, or for multi-GPU scenarios,
// to track that a page is uninitialized on a particular GPU, and needs to be captured for the first time (indicated
// by a CapturedSurfaceCacheFrameIndex value of zero).
// CapturedSurfaceCacheFrameIndex, PageTableIndex
FBinaryHeap<uint32, uint32> LastCapturedPageHeap[MAX_NUM_GPUS];
// Data structures needed to support sharing cards between primitive groups
TMap<FLumenCardId, TSparseArray<FLumenCardSharingInfo>> CardSharingInfoMap;
TArray<FLumenCardSharingInfoPendingRemove> PendingRemoveCardSharingInfos;
TArray<FLumenCardSharingInfoPendingAdd> PendingAddCardSharingInfos;
};