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

2137 lines
78 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LumenMeshCards.h"
#include "RendererPrivate.h"
#include "Lumen.h"
#include "MeshCardBuild.h"
#include "ComponentRecreateRenderStateContext.h"
#include "InstanceDataSceneProxy.h"
#include "VT/RuntimeVirtualTextureSceneProxy.h"
#include "Hash/xxhash.h"
int32 GLumenSceneGlobalDFResolution = 252;
FAutoConsoleVariableRef CVarLumenSceneGlobalDFResolution(
TEXT("r.LumenScene.GlobalSDF.Resolution"),
GLumenSceneGlobalDFResolution,
TEXT("Global Distance Field resolution when Lumen is enabled."),
ECVF_RenderThreadSafe
);
float GLumenSceneGlobalDFClipmapExtent = 2500.0f;
FAutoConsoleVariableRef CVarLumenSceneGlobalDFClipmapExtent(
TEXT("r.LumenScene.GlobalSDF.ClipmapExtent"),
GLumenSceneGlobalDFClipmapExtent,
TEXT("Global Distance Field first clipmap extent when Lumen is enabled."),
ECVF_RenderThreadSafe
);
static TAutoConsoleVariable<int32> CVarLumenFarField(
TEXT("r.LumenScene.FarField"),
0,
TEXT("Enable/Disable Lumen far-field ray tracing."),
FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable)
{
// Recreate proxies so that FPrimitiveSceneProxy::UpdateVisibleInLumenScene() can pick up any changed state
FGlobalComponentRecreateRenderStateContext Context;
}),
ECVF_Scalability | ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarLumenFarFieldOcclusionOnly(
TEXT("r.LumenScene.FarField.OcclusionOnly"),
0,
TEXT("Whether to speedup Far Field traces by only calculating occlusion."),
FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable)
{
// Recreate proxies so that FPrimitiveSceneProxy::UpdateVisibleInLumenScene() can pick up any changed state
FGlobalComponentRecreateRenderStateContext Context;
}),
ECVF_Scalability | ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarLumenFarFieldMaxTraceDistance(
TEXT("r.LumenScene.FarField.MaxTraceDistance"),
1.0e6f,
TEXT("Maximum hit-distance for Lumen far-field ray tracing (Default = 1.0e6)."),
ECVF_Scalability | ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarLumenFarFieldDitherScale(
TEXT("r.LumenScene.FarField.FarFieldDitherScale"),
200.0f,
TEXT("Dither region between near and far field in world space units."),
ECVF_Scalability | ECVF_RenderThreadSafe);
TAutoConsoleVariable<int32> CVarLumenSceneUpdateViewOrigin(
TEXT("r.LumenScene.UpdateViewOrigin"),
1,
TEXT("Whether to update view origin for voxel lighting and global distance field. Useful for debugging."),
ECVF_RenderThreadSafe
);
static TAutoConsoleVariable<int32> CVarLumenSceneSurfaceCacheAtlasSize(
TEXT("r.LumenScene.SurfaceCache.AtlasSize"),
4096,
TEXT("Surface cache card atlas size."),
ECVF_Scalability | ECVF_RenderThreadSafe
);
namespace Lumen
{
bool UseFarField(const FSceneViewFamily& ViewFamily)
{
return CVarLumenFarField.GetValueOnRenderThread() != 0
&& ViewFamily.EngineShowFlags.LumenFarFieldTraces;
}
bool UseFarFieldOcclusionOnly()
{
return CVarLumenFarFieldOcclusionOnly.GetValueOnRenderThread() != 0;
}
float GetFarFieldMaxTraceDistance()
{
return CVarLumenFarFieldMaxTraceDistance.GetValueOnRenderThread();
}
float GetNearFieldMaxTraceDistanceDitherScale(bool bUseFarField)
{
return bUseFarField ? CVarLumenFarFieldDitherScale.GetValueOnRenderThread() : 0.0f;
}
float GetNearFieldSceneRadius(const FViewInfo& View, bool bUseFarField)
{
float SceneRadius = FLT_MAX;
if (bUseFarField && RayTracing::GetCullingMode(View.Family->EngineShowFlags) != RayTracing::ECullingMode::Disabled)
{
return GetRayTracingCullingRadius();
}
return SceneRadius;
}
bool SetLandscapeHeightSamplingParameters(const FVector& LumenSceneViewOrigin, const FScene* Scene, FLumenLandscapeHeightSamplingParameters& OutParameters)
{
bool bSetToDefault = true;
const FRuntimeVirtualTextureSceneProxy* HeightVirtualTextureProxy = nullptr;
if (Scene)
{
for (const FRuntimeVirtualTextureSceneProxy* VirtualTextureProxy : Scene->RuntimeVirtualTextures)
{
if (VirtualTextureProxy
&& VirtualTextureProxy->GetMaterialType() == ERuntimeVirtualTextureMaterialType::WorldHeight
&& VirtualTextureProxy->GetBounds().IsInsideXY(LumenSceneViewOrigin))
{
HeightVirtualTextureProxy = VirtualTextureProxy;
break;
}
}
}
if (HeightVirtualTextureProxy)
{
if (const IAllocatedVirtualTexture* AllocatedTexture = HeightVirtualTextureProxy->GetAllocatedVirtualTexture())
{
const uint32 LayerIndex = 0;
const uint32 PageTableIndex = 0;
if (FRHIShaderResourceView* PhysicalTextureSRV = AllocatedTexture->GetPhysicalTextureSRV(LayerIndex, false))
{
OutParameters.HeightVirtualTexture = PhysicalTextureSRV;
AllocatedTexture->GetPackedUniform(&OutParameters.HeightVirtualTextureUniforms, LayerIndex);
}
OutParameters.HeightVirtualTexturePageTable = PageTableIndex < AllocatedTexture->GetNumPageTableTextures() ?
AllocatedTexture->GetPageTableTexture(PageTableIndex) : nullptr;
OutParameters.HeightVirtualTexturePageTableIndirection = AllocatedTexture->GetPageTableIndirectionTexture();
OutParameters.HeightVirtualTextureAdaptive = HeightVirtualTextureProxy->IsAdaptive();
OutParameters.HeightVirtualTextureSampler = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
if (OutParameters.HeightVirtualTexturePageTable)
{
FUintVector4 PageTableUniforms[2];
AllocatedTexture->GetPackedPageTableUniform(PageTableUniforms);
OutParameters.HeightVirtualTexturePackedUniform0 = PageTableUniforms[0];
OutParameters.HeightVirtualTexturePackedUniform1 = PageTableUniforms[1];
FVector4 WorldToUVParameters[4];
WorldToUVParameters[0] = HeightVirtualTextureProxy->GetUniformParameter(ERuntimeVirtualTextureShaderUniform_WorldToUVTransform0);
WorldToUVParameters[1] = HeightVirtualTextureProxy->GetUniformParameter(ERuntimeVirtualTextureShaderUniform_WorldToUVTransform1);
WorldToUVParameters[2] = HeightVirtualTextureProxy->GetUniformParameter(ERuntimeVirtualTextureShaderUniform_WorldToUVTransform2);
WorldToUVParameters[3] = HeightVirtualTextureProxy->GetUniformParameter(ERuntimeVirtualTextureShaderUniform_WorldHeightUnpack);
FLargeWorldRenderPosition HeightVirtualTextureOrigin(WorldToUVParameters[0]);
OutParameters.HeightVirtualTextureLWCTile = HeightVirtualTextureOrigin.GetTile();
OutParameters.HeightVirtualTextureWorldToUVTransform = FMatrix44f(
HeightVirtualTextureOrigin.GetOffset(),
FVector3f((FVector4f)WorldToUVParameters[1]),
FVector3f((FVector4f)WorldToUVParameters[2]),
FVector3f((FVector4f)WorldToUVParameters[3]));
OutParameters.HeightVirtualTextureEnabled = 1;
}
bSetToDefault = !OutParameters.HeightVirtualTexture
|| !OutParameters.HeightVirtualTexturePageTable
|| (OutParameters.HeightVirtualTextureAdaptive != 0 && !OutParameters.HeightVirtualTexturePageTableIndirection);
}
}
if (bSetToDefault)
{
OutParameters.HeightVirtualTexture = GBlackTextureWithSRV->ShaderResourceViewRHI;
OutParameters.HeightVirtualTexturePageTable = GBlackUintTexture->TextureRHI;
OutParameters.HeightVirtualTexturePageTableIndirection = GBlackUintTexture->TextureRHI;
OutParameters.HeightVirtualTextureAdaptive = 0;
OutParameters.HeightVirtualTextureLWCTile = FVector3f::ZeroVector;
OutParameters.HeightVirtualTextureWorldToUVTransform = FMatrix44f::Identity;
OutParameters.HeightVirtualTextureEnabled = 0;
OutParameters.HeightVirtualTexturePackedUniform0 = FUintVector4::ZeroValue;
OutParameters.HeightVirtualTexturePackedUniform1 = FUintVector4::ZeroValue;
OutParameters.HeightVirtualTextureUniforms = FUintVector4::ZeroValue;
}
return !bSetToDefault;
}
}
int32 Lumen::GetGlobalDFResolution()
{
return GLumenSceneGlobalDFResolution;
}
float Lumen::GetGlobalDFClipmapExtent(int32 ClipmapIndex)
{
return GLumenSceneGlobalDFClipmapExtent * FMath::Pow(2.0f, ClipmapIndex);
}
int32 Lumen::GetNumGlobalDFClipmaps(const FSceneView& View)
{
return GlobalDistanceField::GetNumGlobalDistanceFieldClipmaps(/*bLumenEnabled*/ true, View.FinalPostProcessSettings.LumenSceneViewDistance);
}
bool Lumen::ShouldUpdateLumenSceneViewOrigin()
{
return CVarLumenSceneUpdateViewOrigin.GetValueOnRenderThread() != 0;
}
FVector Lumen::GetLumenSceneViewOrigin(const FViewInfo& View, int32 ClipmapIndex)
{
FVector CameraOrigin = View.ViewMatrices.GetViewOrigin();
if (View.ViewState)
{
FVector CameraVelocityOffset = View.ViewState->GlobalDistanceFieldData->CameraVelocityOffset;
if (ClipmapIndex > 0)
{
const float ClipmapExtent = Lumen::GetGlobalDFClipmapExtent(ClipmapIndex);
const float MaxCameraDriftFraction = .75f;
CameraVelocityOffset.X = FMath::Clamp<float>(CameraVelocityOffset.X, -ClipmapExtent * MaxCameraDriftFraction, ClipmapExtent * MaxCameraDriftFraction);
CameraVelocityOffset.Y = FMath::Clamp<float>(CameraVelocityOffset.Y, -ClipmapExtent * MaxCameraDriftFraction, ClipmapExtent * MaxCameraDriftFraction);
CameraVelocityOffset.Z = FMath::Clamp<float>(CameraVelocityOffset.Z, -ClipmapExtent * MaxCameraDriftFraction, ClipmapExtent * MaxCameraDriftFraction);
}
CameraOrigin += CameraVelocityOffset;
}
// Frozen camera
if (View.ViewState)
{
if (Lumen::ShouldUpdateLumenSceneViewOrigin())
{
View.ViewState->GlobalDistanceFieldData->bUpdateViewOrigin = true;
}
else
{
if (View.ViewState->GlobalDistanceFieldData->bUpdateViewOrigin)
{
View.ViewState->GlobalDistanceFieldData->LastViewOrigin = View.ViewMatrices.GetViewOrigin();
View.ViewState->GlobalDistanceFieldData->bUpdateViewOrigin = false;
}
}
if (!View.ViewState->GlobalDistanceFieldData->bUpdateViewOrigin)
{
CameraOrigin = View.ViewState->GlobalDistanceFieldData->LastViewOrigin;
}
}
return CameraOrigin;
}
class FLumenCardPageGPUData
{
public:
// Must match usf
enum { DataStrideInFloat4s = 5 };
enum { DataStrideInBytes = DataStrideInFloat4s * sizeof(FVector4f) };
static void FillData(const FLumenPageTableEntry& RESTRICT PageTableEntry, uint32 ResLevelPageTableOffset, FIntPoint ResLevelSizeInTiles, FVector2D InvPhysicalAtlasSize, FVector4f* RESTRICT OutData)
{
// Layout must match GetLumenCardPageData in usf
const float SizeInTexelsX = PageTableEntry.PhysicalAtlasRect.Max.X - PageTableEntry.PhysicalAtlasRect.Min.X;
const float SizeInTexelsY = PageTableEntry.PhysicalAtlasRect.Max.Y - PageTableEntry.PhysicalAtlasRect.Min.Y;
OutData[0].X = *(float*)&PageTableEntry.CardIndex;
OutData[0].Y = *(float*)&ResLevelPageTableOffset;
OutData[0].Z = SizeInTexelsX;
OutData[0].W = SizeInTexelsY;
OutData[1] = PageTableEntry.CardUVRect;
OutData[2].X = PageTableEntry.PhysicalAtlasRect.Min.X * InvPhysicalAtlasSize.X;
OutData[2].Y = PageTableEntry.PhysicalAtlasRect.Min.Y * InvPhysicalAtlasSize.Y;
OutData[2].Z = PageTableEntry.PhysicalAtlasRect.Max.X * InvPhysicalAtlasSize.X;
OutData[2].W = PageTableEntry.PhysicalAtlasRect.Max.Y * InvPhysicalAtlasSize.Y;
OutData[3].X = SizeInTexelsX > 0.0f ? ((PageTableEntry.CardUVRect.Z - PageTableEntry.CardUVRect.X) / SizeInTexelsX) : 0.0f;
OutData[3].Y = SizeInTexelsY > 0.0f ? ((PageTableEntry.CardUVRect.W - PageTableEntry.CardUVRect.Y) / SizeInTexelsY) : 0.0f;
OutData[3].Z = *(float*)&ResLevelSizeInTiles.X;
OutData[3].W = *(float*)&ResLevelSizeInTiles.Y;
const uint32 LastUpdateFrame = 0;
OutData[4].X = *(float*)&LastUpdateFrame;
OutData[4].Y = *(float*)&LastUpdateFrame;
OutData[4].Z = *(float*)&LastUpdateFrame;
// This is only used to rotate through the texels in a quad when using adaptive direct lighting shadow rays.
// So we can store a TemporalIndexMod4 and use only 2 bits if needed.
OutData[4].W = *(float*)&LastUpdateFrame;
static_assert(DataStrideInFloat4s == 5, "Data stride doesn't match");
}
};
static FIntPoint GetDesiredPhysicalAtlasSizeInPages(float SurfaceCacheResolution)
{
int32 AtlasSizeInPages = FMath::DivideAndRoundUp<uint32>(CVarLumenSceneSurfaceCacheAtlasSize.GetValueOnRenderThread(), Lumen::PhysicalPageSize);
AtlasSizeInPages = AtlasSizeInPages * SurfaceCacheResolution;
AtlasSizeInPages = FMath::Clamp(AtlasSizeInPages, 1, 64);
return FIntPoint(AtlasSizeInPages, AtlasSizeInPages);
}
static FIntPoint GetDesiredPhysicalAtlasSize(float SurfaceCacheResolution)
{
return GetDesiredPhysicalAtlasSizeInPages(SurfaceCacheResolution) * Lumen::PhysicalPageSize;
}
FLumenPrimitiveGroupRemoveInfo::FLumenPrimitiveGroupRemoveInfo(const FPrimitiveSceneInfo* InPrimitive, int32 InPrimitiveIndex)
: Primitive(InPrimitive)
, PrimitiveIndex(InPrimitiveIndex)
, LumenPrimitiveGroupIndices(InPrimitive->LumenPrimitiveGroupIndices)
{
}
bool FLumenPrimitiveGroup::HasMergedInstances() const
{
bool HasInstancesToMerge = false;
if (PrimitiveInstanceIndex < 0)
{
// Check if there is more than 1 instance for merging
uint32 NumInstances = 0;
for (const FPrimitiveSceneInfo* PrimitiveSceneInfo : Primitives)
{
NumInstances += PrimitiveSceneInfo->GetNumInstanceSceneDataEntries();
if (NumInstances > 1)
{
HasInstancesToMerge = true;
break;
}
}
}
return HasInstancesToMerge;
}
FLumenSurfaceCacheAllocator::FPageBin::FPageBin(const FIntPoint& InElementSize)
{
ensure(InElementSize.GetMax() <= Lumen::PhysicalPageSize);
ElementSize = InElementSize;
PageSizeInElements = FIntPoint(Lumen::PhysicalPageSize) / InElementSize;
}
void FLumenSurfaceCacheAllocator::Init(const FIntPoint& InPageAtlasSizeInPages)
{
PageAtlasSizeInPages = InPageAtlasSizeInPages;
PhysicalPageFreeCount = InPageAtlasSizeInPages.X * InPageAtlasSizeInPages.Y;
PhysicalPageList.Init(false, PhysicalPageFreeCount);
// Init() can be calls several time if Lumen is enabled/disabled several time during a session
// PageBinLookup needs to be initialized only once. After that, its data needs to be kept as the PageBins
// are never deallocated, and PageBinLookup holds the lookup towards these PageBins.
if (bInitPageBinLookup)
{
PageBinLookup = FPageBinLookup(InPlace, 0xFF /*InvalidPageBinIndex*/);
bInitPageBinLookup = false;
}
}
FIntPoint FLumenSurfaceCacheAllocator::AllocatePhysicalAtlasPage()
{
const int32 LinearIndex = PhysicalPageList.FindAndSetFirstZeroBit();
--PhysicalPageFreeCount;
check(LinearIndex != INDEX_NONE);
return FIntPoint(LinearIndex % PageAtlasSizeInPages.X, LinearIndex / PageAtlasSizeInPages.X);
}
void FLumenSurfaceCacheAllocator::FreePhysicalAtlasPage(const FIntPoint& PageCoord)
{
const uint32 LinearIndex = PageCoord.X + PageCoord.Y * PageAtlasSizeInPages.X;
++PhysicalPageFreeCount;
check(PhysicalPageList.IsValidIndex(LinearIndex));
PhysicalPageList[LinearIndex] = false;
}
void FLumenSurfaceCacheAllocator::Allocate(const FLumenPageTableEntry& Page, FAllocation& Allocation)
{
if (Page.IsSubAllocation())
{
FPageBin* MatchingBin = GetOrAddBin(Page.SubAllocationSize);
check(MatchingBin);
FPageBinAllocation* MatchingBinAllocation = MatchingBin->GetBinAllocation();
if (!MatchingBinAllocation)
{
MatchingBinAllocation = MatchingBin->AddBinAllocation(AllocatePhysicalAtlasPage());
}
if (MatchingBinAllocation)
{
const FIntPoint ElementCoord = MatchingBinAllocation->Add();
check(MatchingBinAllocation->PageCoord.X >= 0 && MatchingBinAllocation->PageCoord.Y >= 0);
const FIntPoint ElementOffset = MatchingBinAllocation->PageCoord * Lumen::PhysicalPageSize + ElementCoord * MatchingBin->ElementSize;
Allocation.PhysicalPageCoord = MatchingBinAllocation->PageCoord;
Allocation.PhysicalAtlasRect.Min = ElementOffset;
Allocation.PhysicalAtlasRect.Max = ElementOffset + MatchingBin->ElementSize;
}
}
else
{
Allocation.PhysicalPageCoord = AllocatePhysicalAtlasPage();
Allocation.PhysicalAtlasRect.Min = (Allocation.PhysicalPageCoord + 0) * Lumen::PhysicalPageSize;
Allocation.PhysicalAtlasRect.Max = (Allocation.PhysicalPageCoord + 1) * Lumen::PhysicalPageSize;
}
}
void FLumenSurfaceCacheAllocator::Free(const FLumenPageTableEntry& Page)
{
if (Page.IsSubAllocation())
{
FPageBin* MatchingBin = GetBin(Page.SubAllocationSize);
check(MatchingBin);
checkSlow(Page.SubAllocationSize == MatchingBin->ElementSize);
if (MatchingBin->RemoveBinAllocation(Page))
{
FreePhysicalAtlasPage(Page.PhysicalPageCoord);
}
}
else
{
FreePhysicalAtlasPage(Page.PhysicalPageCoord);
}
}
/**
* Checks if there's enough free memory in the surface cache to allocate entire mip map level of a card (or a single page)
*/
bool FLumenSurfaceCacheAllocator::IsSpaceAvailable(const FLumenCard& Card, int32 ResLevel, bool bSinglePage) const
{
FLumenMipMapDesc MipMapDesc;
Card.GetMipMapDesc(ResLevel, MipMapDesc);
const int32 ReqSizeInPages = bSinglePage ? 1 : (MipMapDesc.SizeInPages.X * MipMapDesc.SizeInPages.Y);
if (PhysicalPageFreeCount >= ReqSizeInPages)
{
return true;
}
// No free pages, but maybe there's some space in one of the existing bins
if (MipMapDesc.bSubAllocation)
{
if (const FPageBin* MatchingBin = GetBin(MipMapDesc.Resolution))
{
return MatchingBin->HasFreeElements();
}
}
return false;
}
void FLumenSurfaceCacheAllocator::GetStats(FStats& Stats) const
{
Stats.NumFreePages = PhysicalPageFreeCount;
for (const FPageBin& Bin : PageBins)
{
const uint32 NumFreeElements = Bin.GetSubPageFreeCount();
const uint32 NumElementsPerPage = Bin.PageSizeInElements.X * Bin.PageSizeInElements.Y;
const uint32 NumElements = Bin.GetBinAllocationCount() * NumElementsPerPage - NumFreeElements;
Stats.BinNumPages += Bin.GetBinAllocationCount();
Stats.BinNumWastedPages += Bin.GetBinAllocationCount() - FMath::DivideAndRoundUp(NumElements, NumElementsPerPage);
Stats.BinPageFreeTexels += NumFreeElements * Bin.ElementSize.X * Bin.ElementSize.Y;
if (NumElements > 0)
{
FBinStats& NewBinStats = Stats.Bins.AddDefaulted_GetRef();
NewBinStats.ElementSize = Bin.ElementSize;
NewBinStats.NumAllocations = NumElements;
NewBinStats.NumPages = Bin.GetBinAllocationCount();
}
}
struct FSortBySize
{
FORCEINLINE bool operator()(const FBinStats& A, const FBinStats& B) const
{
const int32 AreaA = A.ElementSize.X * A.ElementSize.Y;
const int32 AreaB = B.ElementSize.X * B.ElementSize.Y;
if (AreaA == AreaB)
{
if (A.ElementSize.X == B.ElementSize.X)
{
return A.ElementSize.Y < B.ElementSize.Y;
}
else
{
return A.ElementSize.X < B.ElementSize.X;
}
}
return AreaA < AreaB;
}
};
Stats.Bins.Sort(FSortBySize());
}
void FLumenSceneData::UploadPageTable(FRDGBuilder& GraphBuilder, FRDGScatterUploadBuilder& UploadBuilder, FLumenSceneFrameTemporaries& FrameTemporaries)
{
RDG_EVENT_SCOPE(GraphBuilder, "LumenUploadPageTable");
RDG_GPU_MASK_SCOPE(GraphBuilder, FRHIGPUMask::All());
if (bReuploadSceneRequest)
{
PageTableIndicesToUpdateInBuffer.SetNum(PageTable.Num());
for (int32 PageIndex = 0; PageIndex < PageTable.Num(); ++PageIndex)
{
PageTableIndicesToUpdateInBuffer[PageIndex] = PageIndex;
}
}
const uint32 NumElements = FMath::Max(1024u, FMath::RoundUpToPowerOfTwo(PageTable.Num()));
const int32 NumElementsToUpload = PageTableIndicesToUpdateInBuffer.Num();
// PageTableBuffer
{
const int32 NumBytesPerElement = 2 * sizeof(uint32);
FRDGBuffer* PageTableBufferRDG = ResizeByteAddressBufferIfNeeded(GraphBuilder, PageTableBuffer, NumElements * NumBytesPerElement, TEXT("Lumen.PageTable"));
FrameTemporaries.PageTableBufferSRV = GraphBuilder.CreateSRV(PageTableBufferRDG);
if (NumElementsToUpload > 0)
{
UploadBuilder.AddPass(
GraphBuilder,
PageTableUploadBuffer,
PageTableBufferRDG,
NumElementsToUpload,
NumBytesPerElement,
TEXT("Lumen.PageTableUpload"),
[this] (FRDGScatterUploader& Uploader)
{
for (int32 PageIndex : PageTableIndicesToUpdateInBuffer)
{
if (PageIndex < PageTable.Num())
{
uint32 PackedData[2] = { 0, 0 };
if (PageTable.IsAllocated(PageIndex))
{
const FLumenPageTableEntry& Page = PageTable[PageIndex];
PackedData[0] |= ((Page.SampleAtlasBiasX & 0xFFF) << 0);
PackedData[0] |= ((Page.SampleAtlasBiasY & 0xFFF) << 12);
PackedData[0] |= ((Page.SampleCardResLevelX & 0xF) << 24);
PackedData[0] |= ((Page.SampleCardResLevelY & 0xF) << 28);
PackedData[1] = Page.SamplePageIndex;
}
Uploader.Add(PageIndex, PackedData);
}
}
});
}
}
// CardPageBuffer
{
TRefCountPtr<FRDGPooledBuffer> CardPageBufferOld = CardPageBuffer;
const int32 NumBytesPerElement = FLumenCardPageGPUData::DataStrideInFloat4s * sizeof(FVector4f);
FRDGBuffer* CardPageBufferRDG = ResizeStructuredBufferIfNeeded(GraphBuilder, CardPageBuffer, NumElements * NumBytesPerElement, TEXT("Lumen.PageBuffer"));
FrameTemporaries.CardPageBufferSRV = GraphBuilder.CreateSRV(CardPageBufferRDG);
FrameTemporaries.CardPageBufferUAV = GraphBuilder.CreateUAV(CardPageBufferRDG);
if (NumElementsToUpload > 0)
{
UploadBuilder.AddPass(
GraphBuilder,
CardPageUploadBuffer,
CardPageBufferRDG,
NumElementsToUpload,
NumBytesPerElement,
TEXT("Lumen.CardPageUploadBuffer"),
[this] (FRDGScatterUploader& Uploader)
{
const FVector2D InvPhysicalAtlasSize = FVector2D(1.0f) / GetPhysicalAtlasSize();
FLumenPageTableEntry NullPageTableEntry;
for (int32 PageIndex : PageTableIndicesToUpdateInBuffer)
{
if (PageIndex < PageTable.Num())
{
uint32 ResLevelPageTableOffset = 0;
FIntPoint ResLevelSizeInTiles = FIntPoint(0, 0);
FVector4f* Data = (FVector4f*)Uploader.Add_GetRef(PageIndex);
if (PageTable.IsAllocated(PageIndex) && PageTable[PageIndex].IsMapped())
{
const FLumenPageTableEntry& PageTableEntry = PageTable[PageIndex];
const FLumenCard& Card = Cards[PageTableEntry.CardIndex];
const FLumenSurfaceMipMap& MipMap = Card.GetMipMap(PageTableEntry.ResLevel);
ResLevelPageTableOffset = MipMap.PageTableSpanOffset;
ResLevelSizeInTiles = MipMap.GetSizeInPages() * (Lumen::PhysicalPageSize / Lumen::CardTileSize);
if (PageTableEntry.IsSubAllocation())
{
ResLevelSizeInTiles = PageTableEntry.SubAllocationSize / Lumen::CardTileSize;
}
FLumenCardPageGPUData::FillData(PageTableEntry, ResLevelPageTableOffset, ResLevelSizeInTiles, InvPhysicalAtlasSize, Data);
}
else
{
FLumenCardPageGPUData::FillData(NullPageTableEntry, ResLevelPageTableOffset, ResLevelSizeInTiles, InvPhysicalAtlasSize, Data);
}
}
}
});
}
// Resize also the CardPageLastUsedBuffers
if (CardPageBufferOld != CardPageBuffer)
{
FRDGBufferRef CardPageLastUsedBufferRDG = GraphBuilder.CreateBuffer(
FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), NumElements), TEXT("Lumen.CardPageLastUsedBuffer"));
FRDGBufferRef CardPageHighResLastUsedBufferRDG = GraphBuilder.CreateBuffer(
FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), NumElements), TEXT("Lumen.CardPageHighResLastUsedBuffer"));
AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(CardPageLastUsedBufferRDG), 0);
AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(CardPageHighResLastUsedBufferRDG), 0);
CardPageLastUsedBuffer = GraphBuilder.ConvertToExternalBuffer(CardPageLastUsedBufferRDG);
CardPageHighResLastUsedBuffer = GraphBuilder.ConvertToExternalBuffer(CardPageHighResLastUsedBufferRDG);
}
}
}
FLumenSceneData::FLumenSceneData(EShaderPlatform ShaderPlatform, EWorldType::Type WorldType) :
bFinalLightingAtlasContentsValid(false)
{
static const auto MeshCardCVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.MeshCardRepresentation"));
bTrackAllPrimitives = (DoesPlatformSupportLumenGI(ShaderPlatform))
&& MeshCardCVar->GetValueOnGameThread() != 0
&& WorldType != EWorldType::EditorPreview;
}
FLumenSceneData::FLumenSceneData(bool bInTrackAllPrimitives) :
bFinalLightingAtlasContentsValid(false)
{
bTrackAllPrimitives = bInTrackAllPrimitives;
}
FLumenSceneData::~FLumenSceneData()
{
LLM_SCOPE_BYTAG(Lumen);
for (int32 CardIndex = 0; CardIndex < Cards.Num(); ++CardIndex)
{
if (Cards.IsAllocated(CardIndex))
{
RemoveCardFromAtlas(CardIndex);
}
}
Cards.Reset();
MeshCards.Reset();
}
bool TrackPrimitiveInstanceForLumenScene(const FMatrix& LocalToWorld, const FBox& LocalBoundingBox, bool bEmissiveLightSource)
{
const FVector LocalToWorldScale = LocalToWorld.GetScaleVector();
const FVector ScaledBoundSize = LocalBoundingBox.GetSize() * LocalToWorldScale;
const FVector FaceSurfaceArea(ScaledBoundSize.Y * ScaledBoundSize.Z, ScaledBoundSize.X * ScaledBoundSize.Z, ScaledBoundSize.Y * ScaledBoundSize.X);
const float LargestFaceArea = FaceSurfaceArea.GetMax();
const float MinFaceSurfaceArea = LumenMeshCards::GetCardMinSurfaceArea(bEmissiveLightSource);
return LargestFaceArea > MinFaceSurfaceArea;
}
// Add function is a member of FScene, because it needs to add the primitive to all FLumenSceneData at once
void FScene::LumenAddPrimitive(FPrimitiveSceneInfo* InPrimitive)
{
LLM_SCOPE_BYTAG(Lumen);
if (DefaultLumenSceneData->bTrackAllPrimitives)
{
const FPrimitiveSceneProxy* Proxy = InPrimitive->Proxy;
for (FLumenSceneDataIterator LumenSceneData = GetLumenSceneDataIterator(); LumenSceneData; ++LumenSceneData)
{
// We copy this flag over when creating per-view lumen scene data, validate that it's still the same
check(LumenSceneData->bTrackAllPrimitives == DefaultLumenSceneData->bTrackAllPrimitives);
LumenSceneData->PrimitivesToUpdateMeshCards.Add(InPrimitive->GetIndex());
if (Proxy->IsVisibleInLumenScene())
{
ensure(!LumenSceneData->PendingAddOperations.Contains(InPrimitive));
ensure(!LumenSceneData->PendingUpdateOperations.Contains(InPrimitive));
ensure(InPrimitive->LumenPrimitiveGroupIndices.Num() == 0);
LumenSceneData->PendingAddOperations.Add(InPrimitive);
}
}
if (Proxy->IsLandscapeNaniteProxy())
{
for (FPrimitiveComponentId SourceComponentId : Proxy->GetSourceLandscapeComponentIds())
{
if (SourceComponentId.IsValid())
{
LandscapeToNaniteProxyMap.Add(SourceComponentId, InPrimitive);
}
}
}
}
}
// Update function is a member of FScene, because it needs to update the primitive for all FLumenSceneData at once
void FScene::LumenUpdatePrimitive(FPrimitiveSceneInfo* InPrimitive)
{
LLM_SCOPE_BYTAG(Lumen);
if (DefaultLumenSceneData->bTrackAllPrimitives
&& InPrimitive->Proxy->IsVisibleInLumenScene()
&& InPrimitive->LumenPrimitiveGroupIndices.Num() > 0)
{
for (FLumenSceneDataIterator LumenSceneData = GetLumenSceneDataIterator(); LumenSceneData; ++LumenSceneData)
{
if (!LumenSceneData->PendingUpdateOperations.Contains(InPrimitive)
&& !LumenSceneData->PendingAddOperations.Contains(InPrimitive))
{
LumenSceneData->PendingUpdateOperations.Add(InPrimitive);
}
}
}
}
// Update function is a member of FScene, because it needs to update the primitive for all FLumenSceneData at once
void FScene::LumenInvalidateSurfaceCacheForPrimitive(FPrimitiveSceneInfo* InPrimitive)
{
LLM_SCOPE_BYTAG(Lumen);
if (DefaultLumenSceneData->bTrackAllPrimitives
&& InPrimitive->Proxy->IsVisibleInLumenScene()
&& InPrimitive->LumenPrimitiveGroupIndices.Num() > 0)
{
for (FLumenSceneDataIterator LumenSceneData = GetLumenSceneDataIterator(); LumenSceneData; ++LumenSceneData)
{
if (!LumenSceneData->PendingSurfaceCacheInvalidationOperations.Contains(InPrimitive))
{
LumenSceneData->PendingSurfaceCacheInvalidationOperations.Add(InPrimitive);
}
}
}
}
// Remove function is a member of FScene, because it needs to remove the primitive from all FLumenSceneData at once, mainly due to
// the FLumenPrimitiveGroupRemoveInfo constructor needing access to LumenPrimitiveGroupIndices from FPrimitiveSceneInfo, before it
// gets reset.
void FScene::LumenRemovePrimitive(FPrimitiveSceneInfo* InPrimitive, int32 PrimitiveIndex)
{
LLM_SCOPE_BYTAG(Lumen);
const FPrimitiveSceneProxy* Proxy = InPrimitive->Proxy;
if (DefaultLumenSceneData->bTrackAllPrimitives
&& InPrimitive->Proxy->IsVisibleInLumenScene())
{
for (FLumenSceneDataIterator LumenSceneData = GetLumenSceneDataIterator(); LumenSceneData; ++LumenSceneData)
{
LumenSceneData->PendingAddOperations.Remove(InPrimitive);
LumenSceneData->PendingUpdateOperations.Remove(InPrimitive);
LumenSceneData->PendingSurfaceCacheInvalidationOperations.Remove(InPrimitive);
LumenSceneData->PendingRemoveOperations.Add(FLumenPrimitiveGroupRemoveInfo(InPrimitive, PrimitiveIndex));
}
InPrimitive->LumenPrimitiveGroupIndices.Reset();
}
if (DefaultLumenSceneData->bTrackAllPrimitives && Proxy->IsLandscapeNaniteProxy())
{
for (FPrimitiveComponentId SourceComponentId : Proxy->GetSourceLandscapeComponentIds())
{
LandscapeToNaniteProxyMap.Remove(SourceComponentId);
}
#if DO_CHECK
for (const auto& Pair : LandscapeToNaniteProxyMap)
{
check(Pair.Value != InPrimitive);
}
#endif
}
}
FLumenSceneDataIterator::FLumenSceneDataIterator(const FScene* InScene)
: NextSceneData(InScene->PerViewOrGPULumenSceneData)
{
Scene = InScene;
LumenSceneData = nullptr;
if (InScene->DefaultLumenSceneData)
{
LumenSceneData = InScene->DefaultLumenSceneData;
}
else
{
// Call increment operator to search for GPU or view specific Lumen scene data
operator++();
}
}
FLumenSceneDataIterator& FLumenSceneDataIterator::operator++()
{
if (NextSceneData)
{
LumenSceneData = NextSceneData->Value;
++NextSceneData;
}
else
{
LumenSceneData = nullptr;
}
return *this;
}
void FLumenSceneData::ResetAndConsolidate()
{
// Reset arrays, but keep allocated memory for 1024 elements
PendingAddOperations.Reset();
PendingRemoveOperations.Reset(1024);
PendingUpdateOperations.Reset();
PendingUpdateOperations.Reserve(1024);
PendingSurfaceCacheInvalidationOperations.Reset();
PendingSurfaceCacheInvalidationOperations.Reserve(64);
// Batch consolidate SparseSpanArrays
PrimitiveGroups.Consolidate();
PrimitiveCullingInfos.Shrink();
InstanceCullingInfos.Consolidate();
Heightfields.Consolidate();
MeshCards.Consolidate();
Cards.Consolidate();
PageTable.Consolidate();
}
void FLumenSceneData::UpdatePrimitiveInstanceOffset(int32 PrimitiveIndex)
{
if (bTrackAllPrimitives)
{
PrimitivesToUpdateMeshCards.Add(PrimitiveIndex);
}
}
void UpdateLumenScenePrimitives(FRHIGPUMask GPUMask, FScene* Scene)
{
LLM_SCOPE_BYTAG(Lumen);
TRACE_CPUPROFILER_EVENT_SCOPE(UpdateLumenScenePrimitives);
QUICK_SCOPE_CYCLE_COUNTER(UpdateLumenScenePrimitives);
// Remove primitives
for (FLumenSceneDataIterator LumenSceneData = Scene->GetLumenSceneDataIterator(); LumenSceneData; ++LumenSceneData)
{
TRACE_CPUPROFILER_EVENT_SCOPE(RemoveLumenPrimitives);
QUICK_SCOPE_CYCLE_COUNTER(RemoveLumenPrimitives);
TSparseUniqueList<int32, SceneRenderingAllocator> PrimitiveGroupsToRemove;
// Delete primitives
for (const FLumenPrimitiveGroupRemoveInfo& RemoveInfo : LumenSceneData->PendingRemoveOperations)
{
for (int32 PrimitiveGroupIndex : RemoveInfo.LumenPrimitiveGroupIndices)
{
FLumenPrimitiveGroup& PrimitiveGroup = LumenSceneData->PrimitiveGroups[PrimitiveGroupIndex];
if (PrimitiveGroup.bHeightfield)
{
LumenSceneData->LandscapePrimitives.RemoveSingleSwap(RemoveInfo.Primitive);
}
for (int32 PrimitiveIndex = 0; PrimitiveIndex < PrimitiveGroup.Primitives.Num(); ++PrimitiveIndex)
{
if (PrimitiveGroup.Primitives[PrimitiveIndex] == RemoveInfo.Primitive)
{
PrimitiveGroup.Primitives.RemoveAtSwap(PrimitiveIndex, EAllowShrinking::No);
break;
}
}
PrimitiveGroupsToRemove.Add(PrimitiveGroupIndex);
}
}
// Delete empty Primitive Groups
for (int32 PrimitiveGroupIndex : PrimitiveGroupsToRemove.Array)
{
FLumenPrimitiveGroup& PrimitiveGroup = LumenSceneData->PrimitiveGroups[PrimitiveGroupIndex];
LumenSceneData->RemoveMeshCards(PrimitiveGroupIndex, /*bUpdateCullingInfo*/ false);
if (PrimitiveGroup.RayTracingGroupMapElementId.IsValid())
{
if (PrimitiveGroup.Primitives.Num() == 0)
{
LumenSceneData->RayTracingGroups.RemoveByElementId(PrimitiveGroup.RayTracingGroupMapElementId);
PrimitiveGroup.RayTracingGroupMapElementId = Experimental::FHashElementId();
}
else
{
// Update bounds
FBox WorldSpaceBoundingBox;
WorldSpaceBoundingBox.Init();
for (const FPrimitiveSceneInfo* Primitive : PrimitiveGroup.Primitives)
{
WorldSpaceBoundingBox += Primitive->Proxy->GetBounds().GetBox();
}
LumenSceneData->UpdatePrimitiveGroupCullingInfo(PrimitiveGroup, WorldSpaceBoundingBox);
}
}
else
{
check(PrimitiveGroup.Primitives.Num() == 0);
}
if (PrimitiveGroup.Primitives.Num() == 0)
{
LumenSceneData->RemovePrimitiveGroupCullingInfo(PrimitiveGroup);
LumenSceneData->PrimitiveGroups.RemoveSpan(PrimitiveGroupIndex, 1);
}
LumenSceneData->PrimitiveGroupIndicesToUpdateInBuffer.Add(PrimitiveGroupIndex);
}
}
// Add primitives
for (FLumenSceneDataIterator LumenSceneData = Scene->GetLumenSceneDataIterator(); LumenSceneData; ++LumenSceneData)
{
TRACE_CPUPROFILER_EVENT_SCOPE(AddLumenPrimitives);
QUICK_SCOPE_CYCLE_COUNTER(AddLumenPrimitives);
for (FPrimitiveSceneInfo* ScenePrimitiveInfo : LumenSceneData->PendingAddOperations)
{
FPrimitiveSceneProxy* SceneProxy = ScenePrimitiveInfo->Proxy;
const int32 NumInstances = ScenePrimitiveInfo->GetNumInstanceSceneDataEntries();
const FInstanceSceneDataBuffers *InstanceData = ScenePrimitiveInfo->GetInstanceSceneDataBuffers();
// Instance data must be available on CPU.
check(!InstanceData || !InstanceData->IsInstanceDataGPUOnly());
bool bAnyInstanceValid = false;
{
const FMatrix& PrimitiveToWorld = SceneProxy->GetLocalToWorld();
for (int32 InstanceIndex = 0; InstanceIndex < NumInstances; ++InstanceIndex)
{
FBox LocalBoundingBox = SceneProxy->GetLocalBounds().GetBox();
FMatrix LocalToWorld = PrimitiveToWorld;
if (InstanceData)
{
LocalToWorld = InstanceData->GetInstanceToWorld(InstanceIndex);
LocalBoundingBox = InstanceData->GetInstanceLocalBounds(InstanceIndex).ToBox();
}
if (TrackPrimitiveInstanceForLumenScene(LocalToWorld, LocalBoundingBox, SceneProxy->IsEmissiveLightSource()))
{
bAnyInstanceValid = true;
break;
}
}
}
LumenSceneData->PrimitivesToUpdateMeshCards.Add(ScenePrimitiveInfo->GetIndex());
if (bAnyInstanceValid)
{
auto GetCustomId = [ScenePrimitiveInfo, SceneProxy]()
{
uint32 CustomId = UINT32_MAX;
if (SceneProxy->IsCompatibleWithLumenCardSharing())
{
FXxHash64Builder Hasher;
uint32 RuntimeResourceID;
uint32 HierarchyOffset;
uint32 AssemblyTransformOffset;
uint32 ImposterIndex;
SceneProxy->GetNaniteResourceInfo(RuntimeResourceID, HierarchyOffset, AssemblyTransformOffset, ImposterIndex);
check(RuntimeResourceID != INDEX_NONE);
Hasher.Update(&RuntimeResourceID, sizeof(RuntimeResourceID));
for (const FNaniteShadingBin& ShadingBin : ScenePrimitiveInfo->NaniteShadingBins[ENaniteMeshPass::LumenCardCapture])
{
Hasher.Update(&ShadingBin, sizeof(ShadingBin));
}
const FXxHash64 Hash = Hasher.Finalize();
CustomId = GetTypeHash(Hash);
}
return CustomId;
};
// First try to merge components
extern int32 GLumenMeshCardsMergeComponents;
if (GLumenMeshCardsMergeComponents != 0
&& SceneProxy->GetRayTracingGroupId() != FPrimitiveSceneProxy::InvalidRayTracingGroupId
&& !SceneProxy->IsEmissiveLightSource()
&& SceneProxy->IsOpaqueOrMasked())
{
const Experimental::FHashElementId RayTracingGroupMapElementId = LumenSceneData->RayTracingGroups.FindOrAddId(SceneProxy->GetRayTracingGroupId(), -1);
int32& PrimitiveGroupIndex = LumenSceneData->RayTracingGroups.GetByElementId(RayTracingGroupMapElementId).Value;
if (PrimitiveGroupIndex >= 0)
{
ScenePrimitiveInfo->LumenPrimitiveGroupIndices.Add(PrimitiveGroupIndex);
FLumenPrimitiveGroup& PrimitiveGroup = LumenSceneData->PrimitiveGroups[PrimitiveGroupIndex];
ensure(PrimitiveGroup.RayTracingGroupMapElementId == RayTracingGroupMapElementId && PrimitiveGroup.CustomId == UINT32_MAX);
LumenSceneData->RemoveMeshCards(PrimitiveGroupIndex, /*bUpdateCullingInfo*/ false);
PrimitiveGroup.bValidMeshCards = true;
PrimitiveGroup.Primitives.Add(ScenePrimitiveInfo);
FBox WorldSpaceBoundingBox;
WorldSpaceBoundingBox.Init();
for (const FPrimitiveSceneInfo* PrimitiveInfoInGroup : PrimitiveGroup.Primitives)
{
WorldSpaceBoundingBox += PrimitiveInfoInGroup->Proxy->GetBounds().GetBox();
}
LumenSceneData->UpdatePrimitiveGroupCullingInfo(PrimitiveGroup, WorldSpaceBoundingBox);
}
else
{
PrimitiveGroupIndex = LumenSceneData->PrimitiveGroups.AddSpan(1);
ScenePrimitiveInfo->LumenPrimitiveGroupIndices.Add(PrimitiveGroupIndex);
LumenSceneData->PrimitiveGroupIndicesToUpdateInBuffer.Add(PrimitiveGroupIndex);
FLumenPrimitiveGroup& PrimitiveGroup = LumenSceneData->PrimitiveGroups[PrimitiveGroupIndex];
PrimitiveGroup.RayTracingGroupMapElementId = RayTracingGroupMapElementId;
PrimitiveGroup.PrimitiveInstanceIndex = -1;
PrimitiveGroup.CardResolutionScale = 1.0f;
PrimitiveGroup.MeshCardsIndex = -1;
PrimitiveGroup.bValidMeshCards = true;
PrimitiveGroup.bFarField = SceneProxy->IsRayTracingFarField();
PrimitiveGroup.bHeightfield = false;
PrimitiveGroup.bOpaqueOrMasked = true;
PrimitiveGroup.LightingChannelMask = SceneProxy->GetLightingChannelMask();
PrimitiveGroup.Primitives.Reset();
PrimitiveGroup.Primitives.Add(ScenePrimitiveInfo);
const FRenderBounds WorldSpaceBoundingBox = SceneProxy->GetBounds().GetBox();
const FSparseArrayAllocationInfo AllocInfo = LumenSceneData->PrimitiveCullingInfos.AddUninitialized();
PrimitiveGroup.PrimitiveCullingInfoIndex = AllocInfo.Index;
new (AllocInfo.Pointer) FLumenPrimitiveGroupCullingInfo(WorldSpaceBoundingBox, PrimitiveGroup, PrimitiveGroupIndex);
}
}
else
{
const FMatrix& LocalToWorld = SceneProxy->GetLocalToWorld();
bool bMergedInstances = false;
if (NumInstances > 1)
{
// Check if we can merge all instances into one MeshCards
extern int32 GLumenMeshCardsMergeInstances;
extern float GLumenMeshCardsMergedMaxWorldSize;
const FBox PrimitiveBox = SceneProxy->GetBounds().GetBox();
const FRenderBounds PrimitiveBounds = FRenderBounds(PrimitiveBox);
if (GLumenMeshCardsMergeInstances
&& NumInstances > 1
&& PrimitiveBox.GetSize().GetMax() < GLumenMeshCardsMergedMaxWorldSize)
{
FRenderBounds PrimitiveRelativeBounds;
double TotalInstanceSurfaceArea = 0;
for (int32 InstanceIndex = 0; InstanceIndex < NumInstances; ++InstanceIndex)
{
const FRenderBounds InstanceBounds = InstanceData->GetInstancePrimitiveRelativeBounds(InstanceIndex);
PrimitiveRelativeBounds += InstanceBounds;
const double InstanceSurfaceArea = BoxSurfaceArea((FVector)InstanceBounds.GetExtent());
TotalInstanceSurfaceArea += InstanceSurfaceArea;
}
const double BoundsSurfaceArea = BoxSurfaceArea((FVector)PrimitiveRelativeBounds.GetExtent());
const float SurfaceAreaRatio = BoundsSurfaceArea / TotalInstanceSurfaceArea;
extern float GLumenMeshCardsMergeInstancesMaxSurfaceAreaRatio;
extern float GLumenMeshCardsMergedResolutionScale;
if (SurfaceAreaRatio < GLumenMeshCardsMergeInstancesMaxSurfaceAreaRatio)
{
const int32 PrimitiveGroupIndex = LumenSceneData->PrimitiveGroups.AddSpan(1);
ScenePrimitiveInfo->LumenPrimitiveGroupIndices.Add(PrimitiveGroupIndex);
LumenSceneData->PrimitiveGroupIndicesToUpdateInBuffer.Add(PrimitiveGroupIndex);
FLumenPrimitiveGroup& PrimitiveGroup = LumenSceneData->PrimitiveGroups[PrimitiveGroupIndex];
PrimitiveGroup.PrimitiveInstanceIndex = -1;
PrimitiveGroup.CardResolutionScale = FMath::Sqrt(1.0f / SurfaceAreaRatio) * GLumenMeshCardsMergedResolutionScale;
PrimitiveGroup.MeshCardsIndex = -1;
PrimitiveGroup.HeightfieldIndex = -1;
PrimitiveGroup.bValidMeshCards = true;
PrimitiveGroup.bFarField = SceneProxy->IsRayTracingFarField();
PrimitiveGroup.bHeightfield = false;
PrimitiveGroup.LightingChannelMask = SceneProxy->GetLightingChannelMask();
PrimitiveGroup.bEmissiveLightSource = SceneProxy->IsEmissiveLightSource();
PrimitiveGroup.bOpaqueOrMasked = SceneProxy->IsOpaqueOrMasked();
PrimitiveGroup.Primitives.Reset();
PrimitiveGroup.Primitives.Add(ScenePrimitiveInfo);
const FRenderBounds WorldSpaceBoundingBox = PrimitiveRelativeBounds.ToBox().ShiftBy(InstanceData->GetPrimitiveWorldSpaceOffset());
const FSparseArrayAllocationInfo AllocInfo = LumenSceneData->PrimitiveCullingInfos.AddUninitialized();
PrimitiveGroup.PrimitiveCullingInfoIndex = AllocInfo.Index;
new (AllocInfo.Pointer) FLumenPrimitiveGroupCullingInfo(WorldSpaceBoundingBox, PrimitiveGroup, PrimitiveGroupIndex);
bMergedInstances = true;
}
#define LOG_LUMEN_PRIMITIVE_ADDS 0
#if LOG_LUMEN_PRIMITIVE_ADDS
{
UE_LOG(LogRenderer, Log, TEXT("AddLumenPrimitive %s: Instances: %u, Merged: %u, SurfaceAreaRatio: %.1f"),
*LumenPrimitive.Primitive->Proxy->GetOwnerName().ToString(),
NumInstances,
LumenPrimitive.bMergedInstances ? 1 : 0,
SurfaceAreaRatio);
}
#endif
}
if (!bMergedInstances)
{
const int32 InstanceCullingInfoOffset = LumenSceneData->InstanceCullingInfos.AddSpan(NumInstances);
const FSparseArrayAllocationInfo AllocInfo = LumenSceneData->PrimitiveCullingInfos.AddUninitialized();
const int32 PrimitiveCullingInfoIndex = AllocInfo.Index;
FRenderBounds WorldSpaceBoundingBox;
const uint32 PrimitiveGroupOffset = ScenePrimitiveInfo->LumenPrimitiveGroupIndices.AddDefaulted(NumInstances);
for (int32 InstanceIndex = 0; InstanceIndex < NumInstances; ++InstanceIndex)
{
const int32 PrimitiveGroupIndex = LumenSceneData->PrimitiveGroups.AddSpan(1);
ScenePrimitiveInfo->LumenPrimitiveGroupIndices[PrimitiveGroupOffset + InstanceIndex] = PrimitiveGroupIndex;
LumenSceneData->PrimitiveGroupIndicesToUpdateInBuffer.Add(PrimitiveGroupIndex);
FLumenPrimitiveGroup& PrimitiveGroup = LumenSceneData->PrimitiveGroups[PrimitiveGroupIndex];
PrimitiveGroup.PrimitiveInstanceIndex = InstanceIndex;
PrimitiveGroup.CardResolutionScale = 1.0f;
PrimitiveGroup.MeshCardsIndex = -1;
PrimitiveGroup.HeightfieldIndex = -1;
PrimitiveGroup.PrimitiveCullingInfoIndex = PrimitiveCullingInfoIndex;
PrimitiveGroup.InstanceCullingInfoIndex = InstanceCullingInfoOffset + InstanceIndex;
PrimitiveGroup.bValidMeshCards = true;
PrimitiveGroup.bFarField = SceneProxy->IsRayTracingFarField();
PrimitiveGroup.bHeightfield = false;
PrimitiveGroup.LightingChannelMask = SceneProxy->GetLightingChannelMask();
PrimitiveGroup.bEmissiveLightSource = SceneProxy->IsEmissiveLightSource();
PrimitiveGroup.bOpaqueOrMasked = SceneProxy->IsOpaqueOrMasked();
PrimitiveGroup.Primitives.Reset();
PrimitiveGroup.Primitives.Add(ScenePrimitiveInfo);
PrimitiveGroup.CustomId = GetCustomId();
const FRenderBounds InstanceBounds = InstanceData->GetInstanceWorldBounds(InstanceIndex);
WorldSpaceBoundingBox += InstanceBounds;
new (&LumenSceneData->InstanceCullingInfos[InstanceCullingInfoOffset + InstanceIndex]) FLumenPrimitiveGroupCullingInfo(InstanceBounds, PrimitiveGroup, PrimitiveGroupIndex);
}
new (AllocInfo.Pointer) FLumenPrimitiveGroupCullingInfo(WorldSpaceBoundingBox, InstanceCullingInfoOffset, NumInstances, SceneProxy->IsRayTracingFarField());
}
}
else
{
const int32 PrimitiveGroupIndex = LumenSceneData->PrimitiveGroups.AddSpan(1);
ScenePrimitiveInfo->LumenPrimitiveGroupIndices.Add(PrimitiveGroupIndex);
LumenSceneData->PrimitiveGroupIndicesToUpdateInBuffer.Add(PrimitiveGroupIndex);
FLumenPrimitiveGroup& PrimitiveGroup = LumenSceneData->PrimitiveGroups[PrimitiveGroupIndex];
PrimitiveGroup.PrimitiveInstanceIndex = 0;
PrimitiveGroup.CardResolutionScale = 1.0f;
PrimitiveGroup.MeshCardsIndex = -1;
PrimitiveGroup.HeightfieldIndex = -1;
PrimitiveGroup.bValidMeshCards = true;
PrimitiveGroup.bFarField = SceneProxy->IsRayTracingFarField();
PrimitiveGroup.bHeightfield = SceneProxy->SupportsHeightfieldRepresentation();
PrimitiveGroup.LightingChannelMask = SceneProxy->GetLightingChannelMask();
PrimitiveGroup.bEmissiveLightSource = SceneProxy->IsEmissiveLightSource();
PrimitiveGroup.bOpaqueOrMasked = SceneProxy->IsOpaqueOrMasked();
PrimitiveGroup.Primitives.Reset();
PrimitiveGroup.Primitives.Add(ScenePrimitiveInfo);
PrimitiveGroup.CustomId = GetCustomId();
const FRenderBounds WorldSpaceBoundingBox = SceneProxy->GetBounds().GetBox();
const FSparseArrayAllocationInfo AllocInfo = LumenSceneData->PrimitiveCullingInfos.AddUninitialized();
PrimitiveGroup.PrimitiveCullingInfoIndex = AllocInfo.Index;
new (AllocInfo.Pointer) FLumenPrimitiveGroupCullingInfo(WorldSpaceBoundingBox, PrimitiveGroup, PrimitiveGroupIndex);
if (PrimitiveGroup.bHeightfield)
{
LumenSceneData->LandscapePrimitives.Add(ScenePrimitiveInfo);
}
}
}
}
}
}
// UpdateLumenPrimitives
for (FLumenSceneDataIterator LumenSceneData = Scene->GetLumenSceneDataIterator(); LumenSceneData; ++LumenSceneData)
{
QUICK_SCOPE_CYCLE_COUNTER(UpdateLumenPrimitives);
for (TSet<FPrimitiveSceneInfo*>::TIterator It(LumenSceneData->PendingUpdateOperations); It; ++It)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = *It;
if (PrimitiveSceneInfo->LumenPrimitiveGroupIndices.Num() > 0)
{
const FCardRepresentationData* CardRepresentationData = PrimitiveSceneInfo->Proxy->GetMeshCardRepresentation();
const FMatrix& PrimitiveToWorld = PrimitiveSceneInfo->Proxy->GetLocalToWorld();
const FInstanceSceneDataBuffers *InstanceData = PrimitiveSceneInfo->GetInstanceSceneDataBuffers();
const int32 NumInstances = PrimitiveSceneInfo->GetNumInstanceSceneDataEntries();
bool bUpdatePrimitiveBounds = NumInstances > 1;
int32 PrimitiveCullingInfoIndex = INDEX_NONE;
FRenderBounds PrimitiveWorldBounds;
// Instance data must be available on CPU.
check(!InstanceData || !InstanceData->IsInstanceDataGPUOnly());
for (int32 PrimitiveGroupIndex : PrimitiveSceneInfo->LumenPrimitiveGroupIndices)
{
FLumenPrimitiveGroup& PrimitiveGroup = LumenSceneData->PrimitiveGroups[PrimitiveGroupIndex];
if (PrimitiveGroup.PrimitiveInstanceIndex >= 0)
{
FBox WorldSpaceBoundingBox = PrimitiveSceneInfo->Proxy->GetBounds().GetBox();
if (InstanceData)
{
WorldSpaceBoundingBox = InstanceData->GetInstanceWorldBounds(PrimitiveGroup.PrimitiveInstanceIndex).GetBox();
check(bUpdatePrimitiveBounds || NumInstances == 1);
if (bUpdatePrimitiveBounds)
{
check(PrimitiveCullingInfoIndex == INDEX_NONE || PrimitiveCullingInfoIndex == PrimitiveGroup.PrimitiveCullingInfoIndex);
PrimitiveCullingInfoIndex = PrimitiveGroup.PrimitiveCullingInfoIndex;
PrimitiveWorldBounds += WorldSpaceBoundingBox;
}
}
LumenSceneData->UpdateMeshCards(PrimitiveToWorld, PrimitiveGroup.MeshCardsIndex, CardRepresentationData->MeshCardsBuildData);
LumenSceneData->UpdatePrimitiveGroupCullingInfo(PrimitiveGroup, WorldSpaceBoundingBox);
LumenSceneData->PrimitiveGroupIndicesToUpdateInBuffer.Add(PrimitiveGroupIndex);
}
else
{
// Primitive group is merged from multiple instances or components. In either case, there is no separate primitive bounds to update
bUpdatePrimitiveBounds = false;
}
}
if (bUpdatePrimitiveBounds)
{
const int32 PrimitiveGroupIndex = PrimitiveSceneInfo->LumenPrimitiveGroupIndices.Last();
const FLumenPrimitiveGroup& PrimitiveGroup = LumenSceneData->PrimitiveGroups[PrimitiveGroupIndex];
check(PrimitiveCullingInfoIndex == PrimitiveGroup.PrimitiveCullingInfoIndex);
LumenSceneData->UpdatePrimitiveGroupCullingInfo(PrimitiveGroup, PrimitiveWorldBounds, /*bForcePrimitiveLevel*/ true);
}
}
}
}
// InvalidateLumenSurfaceCacheForPrimitives
for (FLumenSceneDataIterator LumenSceneData = Scene->GetLumenSceneDataIterator(); LumenSceneData; ++LumenSceneData)
{
TRACE_CPUPROFILER_EVENT_SCOPE(InvalidateLumenSurfaceCacheForPrimitives);
QUICK_SCOPE_CYCLE_COUNTER(InvalidateLumenSurfaceCacheForPrimitives);
for (TSet<FPrimitiveSceneInfo*>::TIterator It(LumenSceneData->PendingSurfaceCacheInvalidationOperations); It; ++It)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = *It;
if (PrimitiveSceneInfo->LumenPrimitiveGroupIndices.Num() > 0)
{
const FCardRepresentationData* CardRepresentationData = PrimitiveSceneInfo->Proxy->GetMeshCardRepresentation();
const FMatrix& PrimitiveToWorld = PrimitiveSceneInfo->Proxy->GetLocalToWorld();
for (int32 PrimitiveGroupIndex : PrimitiveSceneInfo->LumenPrimitiveGroupIndices)
{
const FLumenPrimitiveGroup& PrimitiveGroup = LumenSceneData->PrimitiveGroups[PrimitiveGroupIndex];
if (PrimitiveGroup.MeshCardsIndex >= 0)
{
LumenSceneData->InvalidateSurfaceCache(GPUMask, PrimitiveGroup.MeshCardsIndex);
}
}
}
}
}
for (FLumenSceneDataIterator LumenSceneData = Scene->GetLumenSceneDataIterator(); LumenSceneData; ++LumenSceneData)
{
LumenSceneData->ResetAndConsolidate();
}
}
void FLumenSceneData::ReleaseAtlas()
{
RemoveAllMeshCards();
PhysicalAtlasSize = 0;
AlbedoAtlas.SafeRelease();
OpacityAtlas.SafeRelease();
NormalAtlas.SafeRelease();
EmissiveAtlas.SafeRelease();
DepthAtlas.SafeRelease();
DirectLightingAtlas.SafeRelease();
IndirectLightingAtlas.SafeRelease();
RadiosityNumFramesAccumulatedAtlas.SafeRelease();
FinalLightingAtlas.SafeRelease();
TileShadowDownsampleFactorAtlas.SafeRelease();
DiffuseLightingAndSecondMomentHistoryAtlas.SafeRelease();
NumFramesAccumulatedHistoryAtlas.SafeRelease();
RadiosityTraceRadianceAtlas.SafeRelease();
RadiosityTraceHitDistanceAtlas.SafeRelease();
RadiosityProbeSHRedAtlas.SafeRelease();
RadiosityProbeSHGreenAtlas.SafeRelease();
RadiosityProbeSHBlueAtlas.SafeRelease();
}
void FLumenSceneData::RemoveAllMeshCards()
{
LLM_SCOPE_BYTAG(Lumen);
QUICK_SCOPE_CYCLE_COUNTER(RemoveAllCards);
for (int32 PrimitiveGroupIndex = 0; PrimitiveGroupIndex < PrimitiveGroups.Num(); ++PrimitiveGroupIndex)
{
if (PrimitiveGroups.IsAllocated(PrimitiveGroupIndex))
{
RemoveMeshCards(PrimitiveGroupIndex);
}
}
CardSharingInfoMap.Empty();
PendingRemoveCardSharingInfos.Empty();
PendingAddCardSharingInfos.Empty();
}
bool FLumenSceneData::UpdateAtlasSize()
{
const ESurfaceCacheCompression NewCompression = GetSurfaceCacheCompression();
if (PhysicalAtlasSize != GetDesiredPhysicalAtlasSize(SurfaceCacheResolution)
|| PhysicalAtlasCompression != NewCompression
|| CurrentLightingDataFormat != Lumen::GetLightingDataFormat()
|| CurrentCachedLightingPreExposure != Lumen::GetCachedLightingPreExposure())
{
RemoveAllMeshCards();
PhysicalAtlasSize = GetDesiredPhysicalAtlasSize(SurfaceCacheResolution);
SurfaceCacheAllocator.Init(GetDesiredPhysicalAtlasSizeInPages(SurfaceCacheResolution));
PhysicalAtlasCompression = NewCompression;
CurrentLightingDataFormat = Lumen::GetLightingDataFormat();
CurrentCachedLightingPreExposure = Lumen::GetCachedLightingPreExposure();
return true;
}
return false;
}
void FLumenSceneData::MapSurfaceCachePage(const FLumenSurfaceMipMap& MipMap, int32 PageTableIndex, FRHIGPUMask GPUMask)
{
FLumenPageTableEntry& PageTableEntry = PageTable[PageTableIndex];
if (!PageTableEntry.IsMapped())
{
FLumenSurfaceCacheAllocator::FAllocation Allocation;
SurfaceCacheAllocator.Allocate(PageTableEntry, Allocation);
PageTableEntry.PhysicalPageCoord = Allocation.PhysicalPageCoord;
PageTableEntry.PhysicalAtlasRect = Allocation.PhysicalAtlasRect;
if (PageTableEntry.IsMapped())
{
PageTableEntry.SamplePageIndex = PageTableIndex;
PageTableEntry.SampleAtlasBiasX = PageTableEntry.PhysicalAtlasRect.Min.X / Lumen::MinCardResolution;
PageTableEntry.SampleAtlasBiasY = PageTableEntry.PhysicalAtlasRect.Min.Y / Lumen::MinCardResolution;
PageTableEntry.SampleCardResLevelX = MipMap.ResLevelX;
PageTableEntry.SampleCardResLevelY = MipMap.ResLevelY;
for (uint32 GPUIndex = 0; GPUIndex < GNumExplicitGPUsForRendering; GPUIndex++)
{
LastCapturedPageHeap[GPUIndex].Add(
#if WITH_MGPU
GPUMask.Contains(GPUIndex) ? GetSurfaceCacheUpdateFrameIndex() : 0,
#else
GetSurfaceCacheUpdateFrameIndex(),
#endif
PageTableIndex);
}
if (!MipMap.bLocked)
{
UnlockedAllocationHeap.Add(SurfaceCacheFeedback.GetFrameIndex(), PageTableIndex);
}
}
PageTableIndicesToUpdateInBuffer.Add(PageTableIndex);
}
}
void FLumenSceneData::UnmapSurfaceCachePage(bool bLocked, FLumenPageTableEntry& Page, int32 PageIndex)
{
if (Page.IsMapped())
{
for (uint32 GPUIndex = 0; GPUIndex < GNumExplicitGPUsForRendering; GPUIndex++)
{
LastCapturedPageHeap[GPUIndex].Remove(PageIndex);
PagesToRecaptureHeap[GPUIndex].Remove(PageIndex);
}
if (!bLocked)
{
UnlockedAllocationHeap.Remove(PageIndex);
}
SurfaceCacheAllocator.Free(Page);
Page.PhysicalPageCoord.X = -1;
Page.PhysicalPageCoord.Y = -1;
Page.SampleAtlasBiasX = 0;
Page.SampleAtlasBiasY = 0;
Page.SampleCardResLevelX = 0;
Page.SampleCardResLevelY = 0;
}
}
const FLumenCardSharingInfo* FLumenSceneData::FindMatchingCardForCopy(const FLumenCardId& CardId, uint32 ResLevel) const
{
if (bAllowCardSharing && CardId.IsValid())
{
const TSparseArray<FLumenCardSharingInfo>* CardInfos = CardSharingInfoMap.Find(CardId);
if (CardInfos)
{
for (const FLumenCardSharingInfo& CardInfo : *CardInfos)
{
if (ResLevel <= CardInfo.MinAllocatedResLevel)
{
return &CardInfo;
}
}
}
}
return nullptr;
}
void FLumenSceneData::FlushPendingCardSharingInfos()
{
if (!bAllowCardSharing)
{
return;
}
TArray<FLumenCardSharingInfoPendingRemove>& PendingRemoves = PendingRemoveCardSharingInfos;
TArray<FLumenCardSharingInfoPendingAdd>& PendingAdds = PendingAddCardSharingInfos;
Algo::Sort(PendingRemoves);
Algo::Sort(PendingAdds);
const int32 NumRemoves = PendingRemoves.Num();
const int32 NumAdds = PendingAdds.Num();
int32 RemoveIndex = 0;
int32 AddIndex = 0;
while (RemoveIndex < NumRemoves || AddIndex < NumAdds)
{
while (RemoveIndex < NumRemoves && (AddIndex >= NumAdds || PendingRemoves[RemoveIndex].CardId <= PendingAdds[AddIndex].CardId))
{
const FLumenCardSharingInfoPendingRemove& Info = PendingRemoves[RemoveIndex];
const FSetElementId Id = CardSharingInfoMap.FindId(Info.CardId);
TSparseArray<FLumenCardSharingInfo>& CardInfos = CardSharingInfoMap.Get(Id).Value;
CardInfos.RemoveAt(Info.CardSharingListIndex);
if (CardInfos.IsEmpty() && (AddIndex >= NumAdds || Info.CardId < PendingAdds[AddIndex].CardId))
{
CardSharingInfoMap.Remove(Id);
}
++RemoveIndex;
}
while (AddIndex < NumAdds && (RemoveIndex >= NumRemoves || PendingAdds[AddIndex].CardId < PendingRemoves[RemoveIndex].CardId))
{
const FLumenCardSharingInfoPendingAdd& Info = PendingAdds[AddIndex];
TSparseArray<FLumenCardSharingInfo>& CardInfos = CardSharingInfoMap.FindOrAdd(Info.CardId);
FLumenCard& Card = Cards[Info.CardIndex];
check(Info.CardId == Card.CardSharingId && Info.MinAllocatedResLevel == Card.MinAllocatedResLevel && Info.bAxisXFlipped == Card.bAxisXFlipped); //-V1013
Card.CardSharingListIndex = CardInfos.Add(FLumenCardSharingInfo(Info.CardIndex, Info.MinAllocatedResLevel, Info.bAxisXFlipped));
++AddIndex;
}
}
PendingRemoves.Reset();
PendingAdds.Reset();
}
void FLumenSceneData::ReallocVirtualSurface(FLumenCard& Card, int32 CardIndex, int32 ResLevel, bool bLockPages)
{
FLumenSurfaceMipMap& MipMap = Card.GetMipMap(ResLevel);
if (MipMap.PageTableSpanSize > 0 && MipMap.bLocked != bLockPages)
{
// Virtual memory is already allocated, but need to change the bLocked flag for any mapped pages
if (MipMap.bLocked)
{
// Unlock all pages
for (int32 LocalPageIndex = 0; LocalPageIndex < MipMap.SizeInPagesX * MipMap.SizeInPagesY; ++LocalPageIndex)
{
const int32 PageTableIndex = MipMap.GetPageTableIndex(LocalPageIndex);
FLumenPageTableEntry& PageTableEntry = PageTable[PageTableIndex];
if (PageTableEntry.IsMapped())
{
UnlockedAllocationHeap.Add(SurfaceCacheFeedback.GetFrameIndex(), PageTableIndex);
}
}
MipMap.bLocked = false;
}
else
{
// Lock all pages
for (int32 LocalPageIndex = 0; LocalPageIndex < MipMap.SizeInPagesX * MipMap.SizeInPagesY; ++LocalPageIndex)
{
const int32 PageTableIndex = MipMap.GetPageTableIndex(LocalPageIndex);
FLumenPageTableEntry& PageTableEntry = PageTable[PageTableIndex];
if (PageTableEntry.IsMapped())
{
UnlockedAllocationHeap.Remove(PageTableIndex);
}
}
MipMap.bLocked = true;
}
}
else if (MipMap.PageTableSpanSize == 0)
{
// Allocate virtual memory for the given mip map
FLumenMipMapDesc MipMapDesc;
Card.GetMipMapDesc(ResLevel, MipMapDesc);
MipMap.bLocked = bLockPages;
MipMap.SizeInPagesX = MipMapDesc.SizeInPages.X;
MipMap.SizeInPagesY = MipMapDesc.SizeInPages.Y;
MipMap.ResLevelX = MipMapDesc.ResLevelX;
MipMap.ResLevelY = MipMapDesc.ResLevelY;
MipMap.PageTableSpanSize = MipMapDesc.SizeInPages.X * MipMapDesc.SizeInPages.Y;
MipMap.PageTableSpanOffset = PageTable.AddSpan(MipMap.PageTableSpanSize);
for (int32 LocalPageIndex = 0; LocalPageIndex < MipMapDesc.SizeInPages.X * MipMapDesc.SizeInPages.Y; ++LocalPageIndex)
{
const int32 PageTableIndex = MipMap.GetPageTableIndex(LocalPageIndex);
FLumenPageTableEntry& PageTableEntry = PageTable[PageTableIndex];
PageTableEntry.CardIndex = CardIndex;
PageTableEntry.ResLevel = ResLevel;
PageTableEntry.SubAllocationSize = MipMapDesc.bSubAllocation ? MipMapDesc.Resolution : FIntPoint(-1, -1);
PageTableEntry.SampleAtlasBiasX = 0;
PageTableEntry.SampleAtlasBiasY = 0;
PageTableEntry.SampleCardResLevelX = 0;
PageTableEntry.SampleCardResLevelY = 0;
const int32 LocalPageCoordX = LocalPageIndex % MipMapDesc.SizeInPages.X;
const int32 LocalPageCoordY = LocalPageIndex / MipMapDesc.SizeInPages.X;
FVector4f CardUVRect;
CardUVRect.X = float(LocalPageCoordX + 0.0f) / MipMapDesc.SizeInPages.X;
CardUVRect.Y = float(LocalPageCoordY + 0.0f) / MipMapDesc.SizeInPages.Y;
CardUVRect.Z = float(LocalPageCoordX + 1.0f) / MipMapDesc.SizeInPages.X;
CardUVRect.W = float(LocalPageCoordY + 1.0f) / MipMapDesc.SizeInPages.Y;
// Every page has a 0.5 texel border for correct bilinear sampling
// This border is only needed on interior page edges
{
FVector2D CardBorderOffset;
CardBorderOffset = FVector2D(0.5f * (Lumen::PhysicalPageSize - Lumen::VirtualPageSize));
CardBorderOffset.X *= (CardUVRect.Z - CardUVRect.X) / Lumen::PhysicalPageSize;
CardBorderOffset.Y *= (CardUVRect.W - CardUVRect.Y) / Lumen::PhysicalPageSize;
if (LocalPageCoordX > 0)
{
CardUVRect.X -= CardBorderOffset.X;
}
if (LocalPageCoordY > 0)
{
CardUVRect.Y -= CardBorderOffset.Y;
}
if (LocalPageCoordX < MipMapDesc.SizeInPages.X - 1)
{
CardUVRect.Z += CardBorderOffset.X;
}
if (LocalPageCoordY < MipMapDesc.SizeInPages.Y - 1)
{
CardUVRect.W += CardBorderOffset.Y;
}
}
PageTableEntry.CardUVRect = CardUVRect;
PageTableIndicesToUpdateInBuffer.Add(PageTableIndex);
}
Card.UpdateMinMaxAllocatedLevel();
CardIndicesToUpdateInBuffer.Add(CardIndex);
}
if (bAllowCardSharing && bLockPages && Card.CardSharingId.IsValid())
{
check(ResLevel == Card.MinAllocatedResLevel && Card.CardSharingListIndex == INDEX_NONE);
PendingAddCardSharingInfos.Add(FLumenCardSharingInfoPendingAdd(Card.CardSharingId, CardIndex, Card.MinAllocatedResLevel, Card.bAxisXFlipped));
}
}
void FLumenSceneData::FreeVirtualSurface(FLumenCard& Card, uint8 FromResLevel, uint8 ToResLevel)
{
if (Card.IsAllocated())
{
for (uint8 ResLevel = FromResLevel; ResLevel <= ToResLevel; ++ResLevel)
{
FLumenSurfaceMipMap& MipMap = Card.GetMipMap(ResLevel);
if (MipMap.IsAllocated())
{
if (bAllowCardSharing && ResLevel == Card.MinAllocatedResLevel && Card.CardSharingId.IsValid() && Card.CardSharingListIndex >= 0)
{
check(MipMap.bLocked);
PendingRemoveCardSharingInfos.Add(FLumenCardSharingInfoPendingRemove(Card.CardSharingId, Card.CardSharingListIndex));
Card.CardSharingListIndex = INDEX_NONE;
}
// Unmap pages
for (int32 LocalPageIndex = 0; LocalPageIndex < MipMap.SizeInPagesX * MipMap.SizeInPagesY; ++LocalPageIndex)
{
const int32 PageTableIndex = MipMap.GetPageTableIndex(LocalPageIndex);
FLumenPageTableEntry& PageTableEntry = PageTable[PageTableIndex];
UnmapSurfaceCachePage(MipMap.bLocked, PageTableEntry, PageTableIndex);
PageTableEntry = FLumenPageTableEntry();
}
if (MipMap.PageTableSpanSize > 0)
{
PageTable.RemoveSpan(MipMap.PageTableSpanOffset, MipMap.PageTableSpanSize);
for (int32 SpanOffset = 0; SpanOffset < MipMap.PageTableSpanSize; ++SpanOffset)
{
PageTableIndicesToUpdateInBuffer.Add(MipMap.PageTableSpanOffset + SpanOffset);
}
MipMap.PageTableSpanOffset = -1;
MipMap.PageTableSpanSize = 0;
MipMap.bLocked = false;
}
}
}
Card.UpdateMinMaxAllocatedLevel();
}
}
/**
* Remove any empty virtual mip allocations, and flatten page search by walking
* though the sparse mip maps and reusing lower res resident pages
*/
void FLumenSceneData::UpdateCardMipMapHierarchy(FLumenCard& Card)
{
// Remove any mip map virtual allocations, which don't have any pages mapped
for (int32 ResLevel = Card.MinAllocatedResLevel; ResLevel <= Card.MaxAllocatedResLevel; ++ResLevel)
{
FLumenSurfaceMipMap& MipMap = Card.GetMipMap(ResLevel);
if (MipMap.IsAllocated())
{
bool IsAnyPageMapped = false;
for (int32 LocalPageIndex = 0; LocalPageIndex < MipMap.SizeInPagesX * MipMap.SizeInPagesY; ++LocalPageIndex)
{
const int32 PageIndex = MipMap.GetPageTableIndex(LocalPageIndex);
if (GetPageTableEntry(PageIndex).IsMapped())
{
IsAnyPageMapped = true;
break;
}
}
if (!IsAnyPageMapped)
{
FreeVirtualSurface(Card, ResLevel, ResLevel);
}
}
}
Card.UpdateMinMaxAllocatedLevel();
int32 ParentResLevel = Card.MinAllocatedResLevel;
for (int32 ResLevel = ParentResLevel + 1; ResLevel <= Card.MaxAllocatedResLevel; ++ResLevel)
{
FLumenSurfaceMipMap& MipMap = Card.GetMipMap(ResLevel);
if (MipMap.PageTableSpanSize > 0)
{
for (int32 LocalPageIndex = 0; LocalPageIndex < MipMap.SizeInPagesX * MipMap.SizeInPagesY; ++LocalPageIndex)
{
const int32 PageIndex = MipMap.GetPageTableIndex(LocalPageIndex);
FLumenPageTableEntry& PageTableEntry = GetPageTableEntry(PageIndex);
if (!PageTableEntry.IsMapped())
{
FIntPoint LocalPageCoord;
LocalPageCoord.X = LocalPageIndex % MipMap.SizeInPagesX;
LocalPageCoord.Y = LocalPageIndex / MipMap.SizeInPagesX;
FLumenSurfaceMipMap& ParentMipMap = Card.GetMipMap(ParentResLevel);
const FIntPoint ParentLocalPageCoord = (LocalPageCoord * ParentMipMap.GetSizeInPages()) / MipMap.GetSizeInPages();
const int32 ParentLocalPageIndex = ParentLocalPageCoord.X + ParentLocalPageCoord.Y * ParentMipMap.SizeInPagesX;
const int32 ParentPageIndex = ParentMipMap.GetPageTableIndex(ParentLocalPageIndex);
FLumenPageTableEntry& ParentPageTableEntry = GetPageTableEntry(ParentPageIndex);
PageTableEntry.SamplePageIndex = ParentPageTableEntry.SamplePageIndex;
PageTableEntry.SampleAtlasBiasX = ParentPageTableEntry.SampleAtlasBiasX;
PageTableEntry.SampleAtlasBiasY = ParentPageTableEntry.SampleAtlasBiasY;
PageTableEntry.SampleCardResLevelX = ParentPageTableEntry.SampleCardResLevelX;
PageTableEntry.SampleCardResLevelY = ParentPageTableEntry.SampleCardResLevelY;
PageTableIndicesToUpdateInBuffer.Add(PageIndex);
}
}
ParentResLevel = ResLevel;
}
}
}
void FLumenSceneData::CopyInitialData(const FLumenSceneData& SourceSceneData)
{
// bTrackAllPrimitives is assumed to be the same across all FLumenSceneData
bTrackAllPrimitives = SourceSceneData.bTrackAllPrimitives;
PrimitiveGroups = SourceSceneData.PrimitiveGroups;
PrimitiveCullingInfos = SourceSceneData.PrimitiveCullingInfos;
InstanceCullingInfos = SourceSceneData.InstanceCullingInfos;
RayTracingGroups = SourceSceneData.RayTracingGroups;
LandscapePrimitives = SourceSceneData.LandscapePrimitives;
PendingAddOperations = SourceSceneData.PendingAddOperations;
PendingUpdateOperations = SourceSceneData.PendingUpdateOperations;
PendingRemoveOperations = SourceSceneData.PendingRemoveOperations;
// Newly created scene data has no mesh cards yet, so clear mesh card indices
for (FLumenPrimitiveGroup& PrimitiveGroup : PrimitiveGroups)
{
PrimitiveGroup.MeshCardsIndex = -1;
}
for (FLumenPrimitiveGroupCullingInfo& CullingInfo : PrimitiveCullingInfos)
{
CullingInfo.bVisible = false;
}
for (FLumenPrimitiveGroupCullingInfo& CullingInfo : InstanceCullingInfos)
{
CullingInfo.bVisible = false;
}
}
#if WITH_MGPU
// When the GPU mask changes for a view specific Lumen scene, we copy all data cross GPU to avoid transient glitches and
// potentially corrupt data. Copying is expensive, but the assumption is that the GPU index will only change for debugging
// or performance tweaks, not during steady rendering.
void FLumenSceneData::UpdateGPUMask(FRDGBuilder& GraphBuilder, const FLumenSceneFrameTemporaries& FrameTemporaries, FLumenViewState& LumenViewState, FRHIGPUMask ViewGPUMask)
{
check(bViewSpecific == true);
if (!bViewSpecificMaskInitialized)
{
ViewSpecificMask = ViewGPUMask;
bViewSpecificMaskInitialized = true;
}
else
{
if (ViewSpecificMask != ViewGPUMask)
{
FLumenSceneData* LumenSceneData = this;
FRHIGPUMask SourceGPUMask = ViewSpecificMask;
FRHIGPUMask DestGPUMask = ViewGPUMask;
ViewSpecificMask = ViewGPUMask;
// Since we're copying all GPU state cross-GPU, it should now be coherent, and we can copy the per-GPU page state as well
for (uint32 DestGPUIndex : FRHIGPUMask::All())
{
if (!SourceGPUMask.Contains(DestGPUIndex))
{
// Copy operator is deleted, need to manually copy item by item
const FBinaryHeap<uint32, uint32>& SourceHeap = LastCapturedPageHeap[SourceGPUMask.GetFirstIndex()];
FBinaryHeap<uint32, uint32>& DestHeap = LastCapturedPageHeap[DestGPUIndex];
uint32 IndexSize = SourceHeap.GetIndexSize();
for (uint32 Index = 0; Index < IndexSize; Index++)
{
// We add items to heaps in tandem, so all should have the same indices
check(SourceHeap.IsPresent(Index) == DestHeap.IsPresent(Index));
if (SourceHeap.IsPresent(Index))
{
DestHeap.Update(SourceHeap.GetKey(Index), Index);
}
}
}
}
// Make array of resources from FLumenSceneFrameTemporaries, since it's on the stack, and can't be passed to the Pass
TArray<FRDGTextureRef, TFixedAllocator<9>> FrameTemporaryTextures;
#define ADD_LUMEN_FRAME_TEMPORARY(NAME) \
if (FrameTemporaries.NAME) FrameTemporaryTextures.Add(FrameTemporaries.NAME)
ADD_LUMEN_FRAME_TEMPORARY(AlbedoAtlas);
ADD_LUMEN_FRAME_TEMPORARY(OpacityAtlas);
ADD_LUMEN_FRAME_TEMPORARY(NormalAtlas);
ADD_LUMEN_FRAME_TEMPORARY(EmissiveAtlas);
ADD_LUMEN_FRAME_TEMPORARY(DepthAtlas);
ADD_LUMEN_FRAME_TEMPORARY(DirectLightingAtlas);
ADD_LUMEN_FRAME_TEMPORARY(IndirectLightingAtlas);
ADD_LUMEN_FRAME_TEMPORARY(RadiosityNumFramesAccumulatedAtlas);
ADD_LUMEN_FRAME_TEMPORARY(FinalLightingAtlas);
ADD_LUMEN_FRAME_TEMPORARY(DiffuseLightingAndSecondMomentHistoryAtlas);
ADD_LUMEN_FRAME_TEMPORARY(NumFramesAccumulatedHistoryAtlas);
#undef ADD_LUMEN_FRAME_TEMPORARY
AddPass(GraphBuilder, RDG_EVENT_NAME("LumenCrossGPUTransfer"),
[LumenSceneData, LumenView = &LumenViewState, FrameTemporaryTextures, SourceGPUMask, DestGPUMask](FRHICommandList& RHICmdList)
{
uint32 SourceGPUIndex = SourceGPUMask.GetFirstIndex();
TArray<FTransferResourceParams> CrossGPUTransferResources;
for (uint32 DestGPUIndex : FRHIGPUMask::All())
{
if (!SourceGPUMask.Contains(DestGPUIndex))
{
#define TRANSFER_LUMEN_RESOURCE(NAME) \
if (LumenSceneData->NAME) CrossGPUTransferResources.Add(FTransferResourceParams(LumenSceneData->NAME->GetRHI(), SourceGPUIndex, DestGPUIndex, false, false))
TRANSFER_LUMEN_RESOURCE(CardBuffer);
TRANSFER_LUMEN_RESOURCE(MeshCardsBuffer);
TRANSFER_LUMEN_RESOURCE(HeightfieldBuffer);
TRANSFER_LUMEN_RESOURCE(SceneInstanceIndexToMeshCardsIndexBuffer);
TRANSFER_LUMEN_RESOURCE(CardPageBuffer);
TRANSFER_LUMEN_RESOURCE(CardPageLastUsedBuffer);
TRANSFER_LUMEN_RESOURCE(CardPageHighResLastUsedBuffer);
TRANSFER_LUMEN_RESOURCE(AlbedoAtlas);
TRANSFER_LUMEN_RESOURCE(OpacityAtlas);
TRANSFER_LUMEN_RESOURCE(NormalAtlas);
TRANSFER_LUMEN_RESOURCE(EmissiveAtlas);
TRANSFER_LUMEN_RESOURCE(DepthAtlas);
TRANSFER_LUMEN_RESOURCE(DirectLightingAtlas);
TRANSFER_LUMEN_RESOURCE(IndirectLightingAtlas);
TRANSFER_LUMEN_RESOURCE(RadiosityNumFramesAccumulatedAtlas);
TRANSFER_LUMEN_RESOURCE(FinalLightingAtlas);
TRANSFER_LUMEN_RESOURCE(TileShadowDownsampleFactorAtlas);
TRANSFER_LUMEN_RESOURCE(DiffuseLightingAndSecondMomentHistoryAtlas);
TRANSFER_LUMEN_RESOURCE(NumFramesAccumulatedHistoryAtlas);
TRANSFER_LUMEN_RESOURCE(RadiosityTraceRadianceAtlas);
TRANSFER_LUMEN_RESOURCE(RadiosityTraceHitDistanceAtlas);
TRANSFER_LUMEN_RESOURCE(RadiosityProbeSHRedAtlas);
TRANSFER_LUMEN_RESOURCE(RadiosityProbeSHGreenAtlas);
TRANSFER_LUMEN_RESOURCE(RadiosityProbeSHBlueAtlas);
TRANSFER_LUMEN_RESOURCE(PageTableBuffer);
#undef TRANSFER_LUMEN_RESOURCE
for (FRDGTextureRef Texture : FrameTemporaryTextures)
{
CrossGPUTransferResources.Add(FTransferResourceParams(Texture->GetRHI(), SourceGPUIndex, DestGPUIndex, false, false));
}
// Also transfer the view state resources cross GPU
LumenView->AddCrossGPUTransfers(SourceGPUIndex, DestGPUIndex, CrossGPUTransferResources);
}
}
RHICmdList.TransferResources(CrossGPUTransferResources);
});
}
}
}
#endif // WITH_MGPU
/**
* Evict all pages on demand, useful for debugging
*/
void FLumenSceneData::ForceEvictEntireCache()
{
TSparseUniqueList<int32, SceneRenderingAllocator> DirtyCards;
while (EvictOldestAllocation(/*MaxFramesSinceLastUsed*/ 0, DirtyCards))
{
}
for (int32 CardIndex : DirtyCards.Array)
{
FLumenCard& Card = Cards[CardIndex];
UpdateCardMipMapHierarchy(Card);
CardIndicesToUpdateInBuffer.Add(CardIndex);
}
}
bool FLumenSceneData::EvictOldestAllocation(uint32 MaxFramesSinceLastUsed, TSparseUniqueList<int32, SceneRenderingAllocator>& DirtyCards)
{
if (UnlockedAllocationHeap.Num() > 0)
{
const uint32 PageTableIndex = UnlockedAllocationHeap.Top();
const uint32 LastFrameUsed = UnlockedAllocationHeap.GetKey(PageTableIndex);
if (uint32(LastFrameUsed + MaxFramesSinceLastUsed) <= SurfaceCacheFeedback.GetFrameIndex())
{
UnlockedAllocationHeap.Pop();
FLumenPageTableEntry& Page = PageTable[PageTableIndex];
if (Page.IsMapped())
{
UnmapSurfaceCachePage(false, Page, PageTableIndex);
DirtyCards.Add(Page.CardIndex);
}
return true;
}
}
return false;
}
void FLumenSceneData::DumpStats(const FDistanceFieldSceneData& DistanceFieldSceneData, bool bDumpMeshDistanceFields, bool bDumpPrimitiveGroups)
{
const FIntPoint PageAtlasSizeInPages = GetDesiredPhysicalAtlasSizeInPages(SurfaceCacheResolution);
const int32 NumPhysicalPages = PageAtlasSizeInPages.X * PageAtlasSizeInPages.Y;
int32 NumCards = 0;
int32 NumVisibleCards = 0;
FLumenCard::FSurfaceStats SurfaceStats;
for (const FLumenCard& Card : Cards)
{
++NumCards;
if (Card.bVisible)
{
++NumVisibleCards;
Card.GetSurfaceStats(PageTable, SurfaceStats);
}
}
int32 NumPrimitiveGroups = 0;
int32 NumPrimitivesMerged = 0;
int32 NumInstancesMerged = 0;
int32 NumMeshCards = 0;
uint32 NumFarFieldPrimitiveGroups = 0;
uint32 NumFarFieldMeshCards = 0;
uint32 NumFarFieldCards = 0;
FLumenCard::FSurfaceStats FarFieldSurfaceStats;
SIZE_T PrimitiveGroupsAllocatedMemory = PrimitiveGroups.GetAllocatedSize();
PrimitiveGroupsAllocatedMemory += PrimitiveCullingInfos.GetAllocatedSize();
PrimitiveGroupsAllocatedMemory += InstanceCullingInfos.GetAllocatedSize();
for (const FLumenPrimitiveGroup& PrimitiveGroup : PrimitiveGroups)
{
++NumPrimitiveGroups;
if (PrimitiveGroup.HasMergedInstances())
{
for (const FPrimitiveSceneInfo* ScenePrimitive : PrimitiveGroup.Primitives)
{
++NumPrimitivesMerged;
NumInstancesMerged += ScenePrimitive->GetNumInstanceSceneDataEntries();
}
}
if (PrimitiveGroup.MeshCardsIndex >= 0)
{
++NumMeshCards;
}
if (PrimitiveGroup.bFarField)
{
++NumFarFieldPrimitiveGroups;
if (PrimitiveGroup.MeshCardsIndex >= 0)
{
++NumFarFieldMeshCards;
const FLumenMeshCards& MeshCardsInstance = MeshCards[PrimitiveGroup.MeshCardsIndex];
NumFarFieldCards += MeshCardsInstance.NumCards;
for (uint32 LocalCardIndex = 0; LocalCardIndex < MeshCardsInstance.NumCards; ++LocalCardIndex)
{
const FLumenCard& LumenCard = Cards[MeshCardsInstance.FirstCardIndex + LocalCardIndex];
if (LumenCard.IsAllocated())
{
LumenCard.GetSurfaceStats(PageTable, FarFieldSurfaceStats);
}
}
}
}
PrimitiveGroupsAllocatedMemory += PrimitiveGroup.Primitives.GetAllocatedSize();
}
FLumenSurfaceCacheAllocator::FStats AllocatorStats;
SurfaceCacheAllocator.GetStats(AllocatorStats);
UE_LOG(LogRenderer, Log, TEXT("*** LumenScene Stats ***"));
UE_LOG(LogRenderer, Log, TEXT(" Mesh SDF Objects: %d"), DistanceFieldSceneData.NumObjectsInBuffer);
UE_LOG(LogRenderer, Log, TEXT(" Primitive groups: %d"), NumPrimitiveGroups);
UE_LOG(LogRenderer, Log, TEXT(" Merged primitives: %d"), NumPrimitivesMerged);
UE_LOG(LogRenderer, Log, TEXT(" Merged instances: %d"), NumInstancesMerged);
UE_LOG(LogRenderer, Log, TEXT(" Mesh cards: %d"), NumMeshCards);
UE_LOG(LogRenderer, Log, TEXT(" Cards: %d"), NumCards);
UE_LOG(LogRenderer, Log, TEXT("*** Surface cache ***"));
UE_LOG(LogRenderer, Log, TEXT(" Allocated %d physical pages out of %d"), NumPhysicalPages - AllocatorStats.NumFreePages, NumPhysicalPages);
UE_LOG(LogRenderer, Log, TEXT(" Bin pages: %d, wasted pages: %d, free texels: %.3fM"), AllocatorStats.BinNumPages, AllocatorStats.BinNumWastedPages, AllocatorStats.BinPageFreeTexels / (1024.0f * 1024.0f));
UE_LOG(LogRenderer, Log, TEXT(" Virtual texels: %.3fM"), SurfaceStats.NumVirtualTexels / (1024.0f * 1024.0f));
UE_LOG(LogRenderer, Log, TEXT(" Locked virtual texels: %.3fM"), SurfaceStats.NumLockedVirtualTexels / (1024.0f * 1024.0f));
UE_LOG(LogRenderer, Log, TEXT(" Physical texels: %.3fM, usage: %.3f%%"), SurfaceStats.NumPhysicalTexels / (1024.0f * 1024.0f), (100.0f * SurfaceStats.NumPhysicalTexels) / (PhysicalAtlasSize.X * PhysicalAtlasSize.Y));
UE_LOG(LogRenderer, Log, TEXT(" Locked Physical texels: %.3fM, usage: %.3f%%"), SurfaceStats.NumLockedPhysicalTexels / (1024.0f * 1024.0f), (100.0f * SurfaceStats.NumLockedPhysicalTexels) / (PhysicalAtlasSize.X * PhysicalAtlasSize.Y));
UE_LOG(LogRenderer, Log, TEXT(" Dropped res levels: %u"), SurfaceStats.DroppedResLevels);
UE_LOG(LogRenderer, Log, TEXT(" Mesh cards to add: %d"), NumMeshCardsToAdd);
UE_LOG(LogRenderer, Log, TEXT(" Locked cards to update: %d"), NumLockedCardsToUpdate);
UE_LOG(LogRenderer, Log, TEXT(" Hi-res pages to add: %d"), NumHiResPagesToAdd);
UE_LOG(LogRenderer, Log, TEXT("*** Far Field ***"));
UE_LOG(LogRenderer, Log, TEXT(" Primitive groups: %d"), NumFarFieldPrimitiveGroups);
UE_LOG(LogRenderer, Log, TEXT(" Mesh cards: %d"), NumFarFieldMeshCards);
UE_LOG(LogRenderer, Log, TEXT(" Cards: %d"), NumFarFieldCards);
UE_LOG(LogRenderer, Log, TEXT(" Virtual texels: %.3fM"), FarFieldSurfaceStats.NumVirtualTexels / (1024.0f * 1024.0f));
UE_LOG(LogRenderer, Log, TEXT(" Locked virtual texels: %.3fM"), FarFieldSurfaceStats.NumLockedVirtualTexels / (1024.0f * 1024.0f));
UE_LOG(LogRenderer, Log, TEXT(" Physical texels: %.3fM, usage: %.3f%%"), FarFieldSurfaceStats.NumPhysicalTexels / (1024.0f * 1024.0f), (100.0f * FarFieldSurfaceStats.NumPhysicalTexels) / (PhysicalAtlasSize.X * PhysicalAtlasSize.Y));
UE_LOG(LogRenderer, Log, TEXT(" Locked Physical texels: %.3fM, usage: %.3f%%"), FarFieldSurfaceStats.NumLockedPhysicalTexels / (1024.0f * 1024.0f), (100.0f * FarFieldSurfaceStats.NumLockedPhysicalTexels) / (PhysicalAtlasSize.X * PhysicalAtlasSize.Y));
UE_LOG(LogRenderer, Log, TEXT(" Dropped res levels: %u"), FarFieldSurfaceStats.DroppedResLevels);
UE_LOG(LogRenderer, Log, TEXT("*** Surface cache Bin Allocator ***"));
for (const FLumenSurfaceCacheAllocator::FBinStats& Bin : AllocatorStats.Bins)
{
UE_LOG(LogRenderer, Log, TEXT(" %3d,%3d bin has %5d allocations using %3d pages"), Bin.ElementSize.X, Bin.ElementSize.Y, Bin.NumAllocations, Bin.NumPages);
}
UE_LOG(LogRenderer, Log, TEXT("*** CPU Memory ***"));
UE_LOG(LogRenderer, Log, TEXT(" Primitive groups allocated memory: %.3fMb"), PrimitiveGroupsAllocatedMemory / (1024.0f * 1024.0f));
UE_LOG(LogRenderer, Log, TEXT(" Cards allocated memory: %.3fMb"), Cards.GetAllocatedSize() / (1024.0f * 1024.0f));
UE_LOG(LogRenderer, Log, TEXT(" MeshCards allocated memory: %.3fMb"), MeshCards.GetAllocatedSize() / (1024.0f * 1024.0f));
UE_LOG(LogRenderer, Log, TEXT("*** GPU Memory ***"));
UE_LOG(LogRenderer, Log, TEXT(" CardBuffer: %.3fMb"), TryGetSize(CardBuffer) / (1024.0f * 1024.0f));
UE_LOG(LogRenderer, Log, TEXT(" MeshCardsBuffer: %.3fMb"), TryGetSize(MeshCardsBuffer) / (1024.0f * 1024.0f));
UE_LOG(LogRenderer, Log, TEXT(" PageTable: %.3fMb"), TryGetSize(PageTableBuffer) / (1024.0f * 1024.0f));
UE_LOG(LogRenderer, Log, TEXT(" CardPages: %.3fMb"), TryGetSize(CardPageBuffer) / (1024.0f * 1024.0f));
UE_LOG(LogRenderer, Log, TEXT(" SceneInstanceIndexToMeshCardsIndexBuffer: %.3fMb"), TryGetSize(SceneInstanceIndexToMeshCardsIndexBuffer) / (1024.0f * 1024.0f));
if (bDumpMeshDistanceFields)
{
DistanceFieldSceneData.ListMeshDistanceFields(true);
}
if (bDumpPrimitiveGroups)
{
#if STATS
UE_LOG(LogRenderer, Log, TEXT("*** LumenScene Primitives ***"));
for (const FLumenPrimitiveGroup& PrimitiveGroup : PrimitiveGroups)
{
for (const FPrimitiveSceneInfo* ScenePrimitive : PrimitiveGroup.Primitives)
{
if (ScenePrimitive && ScenePrimitive->Proxy)
{
UE_LOG(LogRenderer, Log, TEXT("Group:%d InstanceIndex:%d FarField:%d Heightfield:%d %s"),
PrimitiveGroup.RayTracingGroupMapElementId.GetIndex(),
PrimitiveGroup.PrimitiveInstanceIndex,
PrimitiveGroup.bFarField ? 1 : 0,
PrimitiveGroup.bHeightfield ? 1 : 0,
*ScenePrimitive->Proxy->GetStatId().GetName().ToString());
}
}
}
#endif
}
}